layui.define(function (exports) { "use strict"; class WeChatJsonCrypto { constructor(token, encodingAesKey, appId) { if (!token || !appId || encodingAesKey.length !== 43 || !/^[A-Za-z0-9+\/]{43}$/.test(encodingAesKey)) { throw new Error("配置错误:Token/AppID不能为空,EncodingAESKey需为43位标准Base64字符"); } this.token = token; this.appId = appId; this.blockSize = 32; this.encodingAesKey = CryptoJS.enc.Base64.parse(encodingAesKey + '='); if (!this.encodingAesKey.words?.length) throw new Error("密钥解析失败"); } // ✅ 核心修复1:加密时直接传入明文WordArray(非{ ciphertext: ... }) async encrypt(data) { const jsonBytes = new TextEncoder().encode(JSON.stringify(data, null, 0)); const appIdBytes = new TextEncoder().encode(this.appId); const random = crypto.getRandomValues(new Uint8Array(16)); const lenBytes = new Uint8Array(new DataView(new ArrayBuffer(4)).buffer); new DataView(lenBytes.buffer).setUint32(0, jsonBytes.length, false); // 拼接明文: random(16) + len(4) + json + appId const plain = new Uint8Array(20 + jsonBytes.length + appIdBytes.length); plain.set(random); plain.set(lenBytes, 16); plain.set(jsonBytes, 20); plain.set(appIdBytes, 20 + jsonBytes.length); // PKCS#7填充 const pad = this.blockSize - (plain.length % this.blockSize); const padded = new Uint8Array(plain.length + pad).fill(pad, plain.length); padded.set(plain); const iv = CryptoJS.lib.WordArray.create(this.encodingAesKey.words.slice(0, 4)); const encrypted = CryptoJS.AES.encrypt( CryptoJS.lib.WordArray.create(padded), // ← 修复点:直接传明文 this.encodingAesKey, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding } ); return CryptoJS.enc.Base64.stringify(encrypted.ciphertext); } async decrypt(encryptedBase64) { try { const cipherWA = CryptoJS.enc.Base64.parse(encryptedBase64); if (cipherWA.sigBytes < 32) return null; const iv = CryptoJS.lib.WordArray.create(this.encodingAesKey.words.slice(0, 4)); const decryptedWA = CryptoJS.AES.decrypt( { ciphertext: cipherWA }, this.encodingAesKey, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding } ); // 转Uint8Array const decrypted = new Uint8Array(decryptedWA.sigBytes); for (let i = 0; i < decryptedWA.sigBytes; i++) { decrypted[i] = (decryptedWA.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; } // ✅ 核心修复2:严格PKCS#7填充验证与移除 const padLen = decrypted[decrypted.length - 1]; if (padLen < 1 || padLen > this.blockSize || decrypted.length < padLen) return null; for (let i = decrypted.length - padLen; i < decrypted.length; i++) { if (decrypted[i] !== padLen) return null; } const plain = decrypted.slice(0, -padLen); // 移除填充 if (plain.length < 20) return null; const jsonLen = new DataView(plain.slice(16, 20).buffer).getUint32(0, false); if (jsonLen <= 0 || 20 + jsonLen > plain.length) return null; const jsonStr = new TextDecoder().decode(plain.slice(20, 20 + jsonLen)); // ✅ 核心修复3:AppID直接解码(无字符过滤) const fromAppId = new TextDecoder().decode(plain.slice(20 + jsonLen)); if (fromAppId !== this.appId) return null; return JSON.parse(jsonStr); } catch { return null; } } generateSignature(timestamp, nonce, encryptMsg) { return CryptoJS.SHA1([this.token, timestamp, nonce, encryptMsg].sort().join('')).toString(CryptoJS.enc.Hex); } } // 导出模块 exports('JsonCrypto', WeChatJsonCrypto); });