AES-XTS运算的浏览器JS实现(本地网页cdn引入aes-js库)
使用说明
保存为html之后,浏览器打开。因网页以cdn方式引入aes-js库,需要联网。
界面预览

测试数据集
NIST 官方测试数据集: https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES#XTS
html src
<!DOCTYPE html>
<html>
<head>
<title>AES-XTS 模式分步演示</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/aes-js/3.1.2/index.min.js"></script>
<style>
body { font-family: monospace; margin: 20px }
.container { display: flex; gap: 20px }
.input-area, .output-area { width: 50%; }
textarea, input { width: 100%; margin-bottom: 10px }
.log {
background: #f5f5f5;
padding: 15px;
height: 400px;
overflow-y: scroll;
/* 保留空白字符和换行符 */
white-space: pre-wrap;
}
.step { margin-bottom: 5px; }
.error { color: red; }
/* 新增 label 样式 */
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
</style>
</head>
<body>
<h1>AES-XTS 模式分步计算演示</h1>
<div class="container">
<div class="input-area">
<!-- 为明文输入框添加 label -->
<label for="plaintext">明文 (16进制字符串,如 a0a1a2...)</label>
<textarea id="plaintext" placeholder="明文 (16进制字符串,如 a0a1a2...)">000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff</textarea>
<!-- 为密钥输入框添加 label -->
<label for="key">密钥 (32字节64字符16进制,如 2b7e...4f3c)</label>
<textarea id="key" placeholder="密钥 (32字节64字符16进制,如 2b7e...4f3c)">27182818284590452353602874713526 31415926535897932384626433832795</textarea>
<!-- 为扇区号输入框添加 label -->
<label for="sector">扇区号(若显式指定Tweak,则以Tweak计算)</label>
<input type="number" id="sector" value="0" placeholder="扇区号">
<!-- 添加 tweak 输入框 -->
<label for="tweak">Tweak (16字节32字符16进制,可选,非空则需输入16字节)</label>
<input type="text" id="tweak" placeholder="Tweak (16字节32字符16进制,可选)">
<button onclick="encrypt()">加密</button>
<button onclick="decrypt()">解密</button>
</div>
<div class="output-area">
<h3>分步日志:</h3>
<div id="log" class="log"></div>
</div>
</div>
<script>
const logElement = document.getElementById('log');
function logStep(message) {
const stepDiv = document.createElement('div');
stepDiv.className = 'step';
stepDiv.textContent = message;
logElement.appendChild(stepDiv);
logElement.scrollTop = logElement.scrollHeight;
}
function logError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'step error';
errorDiv.textContent = message;
logElement.appendChild(errorDiv);
logElement.scrollTop = logElement.scrollHeight;
}
// 根据 AES - XTS 规范实现的 GF(2^128) 乘 α 函数
function gfMultiply(x) {
// 将 BigInt 转换为 16 字节的 Uint8Array
const bytes = new Uint8Array(16);
for (let i = 15; i >= 0; i--) {
bytes[i] = Number(x & 0xffn);
x >>= 8n;
}
const result = new Uint8Array(16);
// 计算 k = 0 的情况
result[0] = (2 * (bytes[0] % 128)) ^ (135 * Math.floor(bytes[15] / 128));
// 计算 k = 1 到 15 的情况
for (let k = 1; k < 16; k++) {
result[k] = (2 * (bytes[k] % 128)) ^ Math.floor(bytes[k - 1] / 128);
}
// 将结果数组转换回 BigInt
let resultBigInt = 0n;
for (let byte of result) {
resultBigInt = (resultBigInt << 8n) | BigInt(byte);
}
return resultBigInt;
}
// XTS模式核心实现
class AES_XTS {
constructor(key) {
try {
const keyBytes = aesjs.utils.hex.toBytes(key);
if (keyBytes.length !== 32) throw new Error("密钥需64字符16进制");
this.key1 = keyBytes.slice(0, 16);
this.key2 = keyBytes.slice(16, 32);
this.aes1 = new aesjs.ModeOfOperation.ecb(this.key1);
this.aes2 = new aesjs.ModeOfOperation.ecb(this.key2);
logStep(`[1] 密钥分割: Key1=${aesjs.utils.hex.fromBytes(this.key1)}, Key2=${aesjs.utils.hex.fromBytes(this.key2)}`);
} catch (e) {
logError(`初始化错误: ${e.message}`);
throw e;
}
}
encrypt(plaintext, sector, tweakHex) {
try {
let tweak;
if (tweakHex) {
// 使用用户输入的 tweak
tweak = aesjs.utils.hex.toBytes(tweakHex);
if (tweak.length !== 16) throw new Error("Tweak 需32字符16进制");
logStep(`[2] 使用用户输入的原始Tweak: T=${tweakHex}`);
// 对用户输入的 tweak 进行 Key2 加密
tweak = this.aes2.encrypt(tweak);
logStep(`[3] ENC_key2(用户输入的Tweak), 块0 Tweak: T=${aesjs.utils.hex.fromBytes(tweak)}`);
} else {
// 基于扇区号生成 tweak
logStep(`[2] 生成初始Tweak: 扇区号=${sector}`);
tweak = new Uint8Array(16);
const view = new DataView(tweak.buffer);
// 修正:使用大端字节序,将扇区号放在低 64 位
view.setBigUint64(8, BigInt(sector), false);
tweak = this.aes2.encrypt(tweak);
logStep(`[3] ENC_key2(${sector}), 块0 Tweak: T=${aesjs.utils.hex.fromBytes(tweak)}`);
}
const blocks = [];
const blockCount = Math.ceil(plaintext.length / 16);
for (let i = 0; i < blockCount; i++) {
// 获取当前块,如果不够16字节则填充0
const block = new Uint8Array(16);
const start = i * 16;
const end = Math.min(start + 16, plaintext.length);
block.set(plaintext.slice(start, end));
// 处理Tweak
const blockTweak = new Uint8Array(tweak);
logStep(`[4.${i}] 块${i} Tweak:\n ${aesjs.utils.hex.fromBytes(blockTweak)}`);
// 明文与Tweak异或
const mixed = block.map((val, idx) => val ^ blockTweak[idx]);
logStep(`[5.${i}] 块${i}明文⊕Tweak:\n ${aesjs.utils.hex.fromBytes(mixed)}`);
// AES加密
const encrypted = this.aes1.encrypt(mixed);
logStep(`[6.${i}] AES加密[5.${i}]结果:\n ${aesjs.utils.hex.fromBytes(encrypted)}`);
// 再次异或Tweak
const cipherBlock = encrypted.map((val, idx) => val ^ blockTweak[idx]);
logStep(`[7.${i}] 密文块(由[6.${i}]结果⊕Tweak):\n ${aesjs.utils.hex.fromBytes(cipherBlock)}`);
blocks.push(...cipherBlock);
// 更新Tweak: T = α • T
const bigIntTweak = BigInt('0x' + aesjs.utils.hex.fromBytes(tweak));
const newTweak = gfMultiply(bigIntTweak);
tweak = new Uint8Array(16);
const newView = new DataView(tweak.buffer);
// 使用大端字节序,正确赋值高 64 位和低 64 位
newView.setBigUint64(0, (newTweak >> 64n) & 0xFFFFFFFFFFFFFFFFn, false); // 高 64 位
newView.setBigUint64(8, newTweak & 0xFFFFFFFFFFFFFFFFn, false); // 低 64 位
}
return new Uint8Array(blocks);
} catch (e) {
logError(`加密过程出错: ${e.message}`);
throw e;
}
}
}
function encrypt() {
try {
logElement.innerHTML = '';
const plaintextHex = document.getElementById('plaintext').value.replace(/\s+/g, '');
const key = document.getElementById('key').value.replace(/\s+/g, '');
const sector = parseInt(document.getElementById('sector').value);
const tweakHex = document.getElementById('tweak').value.replace(/\s+/g, '');
// 验证输入
if (!plaintextHex || !/^[0-9a-fA-F]+$/.test(plaintextHex)) {
logError("错误:明文需为16进制字符");
return;
}
if (!key || key.length !== 64 || !/^[0-9a-fA-F]+$/.test(key)) {
logError("错误:密钥需64字符16进制");
return;
}
if (isNaN(sector)) {
logError("错误:无效的扇区号");
return;
}
if (tweakHex && (!/^[0-9a-fA-F]+$/.test(tweakHex) || tweakHex.length !== 32)) {
logError("错误:Tweak 需32字符16进制");
return;
}
// 执行加密
const plaintext = aesjs.utils.hex.toBytes(plaintextHex);
const xts = new AES_XTS(key);
const ciphertext = xts.encrypt(plaintext, sector, tweakHex);
const ciphertextHex = aesjs.utils.hex.fromBytes(ciphertext);
// 每 32 个字符(16 字节)插入换行符
let formattedCiphertext = ' ';
for (let i = 0; i < ciphertextHex.length; i += 32) {
formattedCiphertext += ciphertextHex.slice(i, i + 32) +'\n ';
}
// 去除最后一个多余的换行符
formattedCiphertext = formattedCiphertext.trimEnd();
logStep(`[8] 最终密文:\n${formattedCiphertext}`);
} catch (e) {
logError(`系统错误: ${e.message}`);
}
}
function decrypt() {
logError("解密功能暂未实现,加密验证可用NIST测试向量");
}
</script>
</body>
</html>

浙公网安备 33010602011771号