Files
YwxAppThink/public/assets/ywxapp/modules/JsonCrypto.js
T

90 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});