You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AES(Rijndael/ECB/NoPadding)在JavaScript中加解密报错排查

对接第三方REST API的AES加密解密问题

加密核心要求

  • 使用Rijndael cipher
  • 采用Electronic Code Book mode (ECB)
  • 不使用内置填充(NoPadding)

集成补充规则

所有URL参数需用共享密钥通过AES加密生成安全令牌,额外要求:

  • 加密后的缓冲区需手动补空格,使总长度为32的倍数(length(encrypted padded string) mod 32 = 0)
  • 示例Java代码:Cipher cipher = Cipher.getInstance("Rijndael/ECB/NoPadding", "SunJCE");

我的JavaScript实现及问题

代码实现

var clearMessage = "The brown fox jumps over the laxy dog";
console.log("Clear message: ", clearMessage);
var encodingKey = CryptoJS.enc.Hex.parse("0123456789ABCDEF0123456789ABCDEF");
var encryptedMessage = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(clearMessage), encodingKey, {
                                            mode: CryptoJS.mode.ECB,
                                            padding: CryptoJS.pad.NoPadding});
console.log("Encrypted message: ", encryptedMessage.ciphertext.toString());


const decryptedMessage = CryptoJS.AES.decrypt(encryptedMessage.ciphertext.toString(), encodingKey, {
                                          mode: CryptoJS.mode.ECB,
                                          padding: CryptoJS.pad.NoPadding});
clearMessage = decryptedMessage.toString(CryptoJS.enc.Utf8);
console.log("Decrypted message: ", clearMessage);

控制台输出

Clear message:  The brown fox jumps over the laxy dog
Encrypted message:  56aaf639f44c106889aa4a765d2bf7c83a7860a379d776982991c7575eb63fd31f7c9ce3db

crypto-js.js:523 Uncaught Error: Malformed UTF-8 data
at Object.stringify (crypto-js.js:523:24)
at WordArray.init.toString (crypto-js.js:278:38)
at encryptDecrypt (index.html:157:39)
at HTMLButtonElement.onclick (index.html:46:60)

错误触发于clearMessage = decryptedMessage.toString(CryptoJS.enc.Utf8);这一行。

ChatGPT生成的代码同样报错

// Import the necessary libraries
var CryptoJS = require("crypto-js");

// Set the plaintext message and secret key
var message = "This is a secret message!";
var secretKey = "ThisIsASecretKey";

// Convert the key and message to WordArrays (required by CryptoJS)
var key = CryptoJS.enc.Utf8.parse(secretKey);
var plaintext = CryptoJS.enc.Utf8.parse(message);

// Encrypt the plaintext message using AES with Rijndael cipher, ECB mode, and no padding
var ciphertext = CryptoJS.AES.encrypt(plaintext, key, {
  mode: CryptoJS.mode.ECB,
  padding: CryptoJS.pad.NoPadding,
  cipher: CryptoJS.algo.Rijndael
});

// Print the encrypted ciphertext in Base64 format
console.log(ciphertext.toString());

// Decrypt the ciphertext message using AES with Rijndael cipher, ECB mode, and no padding
var decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
  mode: CryptoJS.mode.ECB,
  padding: CryptoJS.pad.NoPadding,
  cipher: CryptoJS.algo.Rijndael
});

// Convert the decrypted message back to plaintext and print it
console.log(decrypted.toString(CryptoJS.enc.Utf8));

问题原因及解决思路

核心原因

  1. 加密前未手动补位:使用NoPadding模式时,明文长度必须是Rijndael块大小的整数倍(此处要求32字节)。若明文长度不满足,CryptoJS会直接截断明文到块大小倍数,导致解密后的字节序列不完整或包含无效UTF-8字节,触发编码错误。
  2. 解密参数传递错误:CryptoJS.AES.decrypt直接接收十六进制字符串作为密文时无法正确解析,必须先将其转换为WordArray类型,否则会解密出无效数据。

解决步骤

1. 加密前手动补空格到32字节倍数

按照集成要求对明文补空格:

function padTo32Multiple(str) {
  const padLength = 32 - (str.length % 32);
  return str.padEnd(str.length + padLength, ' ');
}

const paddedMessage = padTo32Multiple(clearMessage);

2. 修正解密的密文解析方式

解密时需将十六进制密文先解析为WordArray:

// 加密后获取十六进制密文
const ciphertextHex = encryptedMessage.ciphertext.toString();

// 解密时传入解析后的WordArray
const decryptedMessage = CryptoJS.AES.decrypt(
  { ciphertext: CryptoJS.enc.Hex.parse(ciphertextHex) }, 
  encodingKey, 
  { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding }
);

3. 解密后去除手动补的空格

恢复原始明文内容:

const decryptedStr = decryptedMessage.toString(CryptoJS.enc.Utf8).trimEnd();

完整修正代码

var clearMessage = "The brown fox jumps over the laxy dog";
console.log("Clear message: ", clearMessage);

// 手动补空格到32字节倍数
function padTo32Multiple(str) {
  const padLength = 32 - (str.length % 32);
  return str.padEnd(str.length + padLength, ' ');
}
const paddedMessage = padTo32Multiple(clearMessage);

var encodingKey = CryptoJS.enc.Hex.parse("0123456789ABCDEF0123456789ABCDEF");

// 加密(显式指定Rijndael块大小为32字节)
var encryptedMessage = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(paddedMessage), encodingKey, {
                                            mode: CryptoJS.mode.ECB,
                                            padding: CryptoJS.pad.NoPadding,
                                            cipher: CryptoJS.algo.Rijndael.create({ blockSize: 32 })
});
const ciphertextHex = encryptedMessage.ciphertext.toString();
console.log("Encrypted message: ", ciphertextHex);

// 解密
const decryptedMessage = CryptoJS.AES.decrypt(
  { ciphertext: CryptoJS.enc.Hex.parse(ciphertextHex) }, 
  encodingKey, 
  { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding,
    cipher: CryptoJS.algo.Rijndael.create({ blockSize: 32 }) }
);

// 去除补位空格并输出
const decryptedStr = decryptedMessage.toString(CryptoJS.enc.Utf8).trimEnd();
console.log("Decrypted message: ", decryptedStr);

额外注意事项

  • CryptoJS默认Rijndael块大小为128位(16字节),需显式指定blockSize:32(256位)以匹配集成要求,加密和解密时都要设置,否则会导致解密失败。

内容的提问来源于stack exchange,提问作者Eddie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 17:45:00