AES-XTS运算的浏览器JS实现(本地网页cdn引入aes-js库)

使用说明

保存为html之后,浏览器打开。因网页以cdn方式引入aes-js库,需要联网。

界面预览

image

测试数据集

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>
posted @ 2025-07-16 13:24  北壹  阅读(25)  评论(0)    收藏  举报