You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
7.3 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 验证将嵌入 index.html 的纯 JS AES-128-ECB 实现与 Node crypto 的一致性
const crypto = require("crypto");
const KEY = "Gi4swf3llyafmsuK";
/* ===== 以下实现与嵌入页面的代码完全一致 ===== */
var AESTool = (function(){
"use strict";
var sbox = new Uint8Array([
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
]);
var invSbox = new Uint8Array(256);
(function(){ for (var i = 0; i < 256; i++) invSbox[sbox[i]] = i; })();
var RCON = [1,2,4,8,16,32,64,128,27,54];
function expandKey(keyBytes){
var rk = new Uint8Array(176), i = 16, rconIdx = 0, j, t;
rk.set(keyBytes);
while (i < 176) {
t = [rk[i-4], rk[i-3], rk[i-2], rk[i-1]];
if (i % 16 === 0) t = [sbox[t[1]] ^ RCON[rconIdx++], sbox[t[2]], sbox[t[3]], sbox[t[0]]];
for (j = 0; j < 4; j++) { rk[i] = rk[i-16] ^ t[j]; i++; }
}
return rk;
}
function xtime(a){ return ((a << 1) ^ (a & 128 ? 27 : 0)) & 255; }
function mul(a, b){
var r = 0;
while (b) { if (b & 1) r ^= a; a = xtime(a); b >>= 1; }
return r & 255;
}
function encryptBlock(b, rk){
var i, c, r, t;
for (i = 0; i < 16; i++) b[i] ^= rk[i];
for (var round = 1; round <= 10; round++) {
for (i = 0; i < 16; i++) b[i] = sbox[b[i]];
// ShiftRows按行循环左移 r
for (r = 1; r < 4; r++) {
var row = [b[r], b[r+4], b[r+8], b[r+12]];
for (c = 0; c < 4; c++) b[r + 4*c] = row[(c + r) % 4];
}
if (round < 10) {
for (c = 0; c < 4; c++) {
var o = 4*c, a0 = b[o], a1 = b[o+1], a2 = b[o+2], a3 = b[o+3];
b[o] = mul(a0,2) ^ mul(a1,3) ^ a2 ^ a3;
b[o+1] = a0 ^ mul(a1,2) ^ mul(a2,3) ^ a3;
b[o+2] = a0 ^ a1 ^ mul(a2,2) ^ mul(a3,3);
b[o+3] = mul(a0,3) ^ a1 ^ a2 ^ mul(a3,2);
}
}
for (i = 0; i < 16; i++) b[i] ^= rk[16*round + i];
}
return b;
}
function decryptBlock(b, rk){
var i, c, r, round;
for (i = 0; i < 16; i++) b[i] ^= rk[160 + i];
for (round = 9; round >= 0; round--) {
if (round < 9) {
for (c = 0; c < 4; c++) {
var o = 4*c, a0 = b[o], a1 = b[o+1], a2 = b[o+2], a3 = b[o+3];
b[o] = mul(a0,14) ^ mul(a1,11) ^ mul(a2,13) ^ mul(a3,9);
b[o+1] = mul(a0,9) ^ mul(a1,14) ^ mul(a2,11) ^ mul(a3,13);
b[o+2] = mul(a0,13) ^ mul(a1,9) ^ mul(a2,14) ^ mul(a3,11);
b[o+3] = mul(a0,11) ^ mul(a1,13) ^ mul(a2,9) ^ mul(a3,14);
}
}
for (r = 1; r < 4; r++) {
var row = [b[r], b[r+4], b[r+8], b[r+12]];
for (c = 0; c < 4; c++) b[r + 4*c] = row[(c - r + 4) % 4];
}
for (i = 0; i < 16; i++) b[i] = invSbox[b[i]];
for (i = 0; i < 16; i++) b[i] ^= rk[16*round + i];
}
return b;
}
function b2a(bytes){ var s = ""; for (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]); return s; }
function a2b(str){ var u = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) u[i] = str.charCodeAt(i) & 255; return u; }
function pkcs7(bytes){
var pad = 16 - (bytes.length % 16), out = new Uint8Array(bytes.length + pad);
out.set(bytes); out.fill(pad, bytes.length);
return out;
}
function encrypt(plainText, keyStr){
if (keyStr.length !== 16) throw new Error("密钥长度必须为 16 位");
var data = pkcs7(new TextEncoder().encode(plainText));
var rk = expandKey(new TextEncoder().encode(keyStr));
var out = "";
for (var off = 0; off < data.length; off += 16) {
var b = data.slice(off, off + 16);
encryptBlock(b, rk);
out += b2a(b);
}
return btoa(out);
}
function decrypt(b64Text, keyStr){
if (keyStr.length !== 16) throw new Error("密钥长度必须为 16 位");
var bin = atob(b64Text.trim());
if (bin.length === 0 || bin.length % 16 !== 0) throw new Error("密文长度不合法(须为 16 字节的整数倍)");
var rk = expandKey(new TextEncoder().encode(keyStr));
var bytes = a2b(bin), out = [];
for (var off = 0; off < bytes.length; off += 16) {
var b = bytes.slice(off, off + 16);
decryptBlock(b, rk);
out.push(b2a(b));
}
var all = out.join("");
var u8 = a2b(all);
var pad = u8[u8.length - 1];
if (pad < 1 || pad > 16) throw new Error("解密失败:填充不合法(密钥错误或密文损坏)");
for (var i = u8.length - pad; i < u8.length; i++) if (u8[i] !== pad) throw new Error("解密失败:填充不合法(密钥错误或密文损坏)");
return new TextDecoder().decode(u8.slice(0, u8.length - pad));
}
return { encrypt: encrypt, decrypt: decrypt };
})();
/* ===== 实现结束 ===== */
// 与 Node crypto 对比验证
function nodeEnc(p){ const e = crypto.createCipheriv("aes-128-ecb", KEY, null); return e.update(p,"utf8","base64") + e.final("base64"); }
function nodeDec(c){ const d = crypto.createDecipheriv("aes-128-ecb", KEY, null); return d.update(c,"base64","utf8") + d.final("utf8"); }
const cases = [
"330725198202084328",
"1",
"330725199001011234",
"a",
"abc123你好身份证测试测试", // 多字节 UTF-8 + 非 16 倍数
"十六个字节正好16byte", // 恰好 32 字节 UTF-8?
"x".repeat(16), // 恰好 16 字节(触发整块填充)
"y".repeat(48) // 多块
];
let fail = 0;
for (const p of cases) {
const mine = AESTool.encrypt(p, KEY);
const ref = nodeEnc(p);
if (mine !== ref) { console.log("ENCRYPT MISMATCH:", JSON.stringify(p), mine, ref); fail++; }
const back = AESTool.decrypt(mine, KEY);
if (back !== p) { console.log("ROUNDTRIP FAIL:", JSON.stringify(p), back); fail++; }
}
const sample = "MoV2Ra2USY/6tc6b9xQXZkuEA+I6X3u90CSNgvFqzlE=";
if (AESTool.decrypt(sample, KEY) !== "330725198202084328") { console.log("SAMPLE DECRYPT FAIL"); fail++; }
if (AESTool.encrypt("330725198202084328", KEY) !== sample) { console.log("SAMPLE ENCRYPT FAIL"); fail++; }
if (nodeDec(AESTool.encrypt("交叉验证文本123", KEY)) !== "交叉验证文本123") { console.log("CROSS DECRYPT FAIL"); fail++; }
console.log(fail === 0 ? "AES IMPLEMENTATION VERIFIED" : "FAILED: " + fail);
process.exit(fail === 0 ? 0 : 1);