chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user