Java AES加密结果Node.js解密报Malformed UTF-8错误求助
问题原因
你遇到的报错核心是Java侧输出的密文结构和CryptoJS默认期望的OpenSSL兼容密文结构不匹配:
- CryptoJS在传入字符串作为密钥调用
CryptoJS.AES.encrypt/decrypt时,默认采用OpenSSL的EVP_BytesToKey算法派生AES密钥和IV,生成/解析的密文结构固定为:[8字节固定标识"Salted__"] + [8字节随机salt] + [AES-CBC密文] - 你的Java加密代码里,密文前8字节写入了无意义的随机数,没有添加CryptoJS期望的"Salted__"标识,导致CryptoJS无法正确提取salt、派生正确的密钥和IV,最终解密得到的内容不是合法UTF-8字符串,抛出对应错误。
修复方案
直接修改Java侧encrypt方法的密文输出逻辑,将前8字节的随机数替换为固定的"Salted__"标识即可:
public static String encrypt(String plain) throws Exception { String result = null; byte[] saltData = RandomUtils.nextBytes(8); MessageDigest md5 = MessageDigest.getInstance("MD5"); final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5); SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES"); IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]); Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCBC.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypted = aesCBC.doFinal(plain.getBytes(StandardCharsets.UTF_8)); // 替换原来的随机8字节,改为CryptoJS期望的Salted__标识 byte[] saltHeader = "Salted__".getBytes(StandardCharsets.UTF_8); byte[] output = new byte[saltHeader.length + saltData.length + encrypted.length]; int i = 0; for(byte b1 : saltHeader){ output[i] = b1; i++; } for(byte b1 : saltData){ output[i] = b1; i++; } for(byte b1 : encrypted){ output[i] = b1; i++; } result = Base64.getEncoder().encodeToString(output); return result; }
修改后Java加密的密文即可直接被现有Node.js代码解密,原有Java侧的解密逻辑不需要调整,依然可以正常自闭环、也可以正常解密Node.js生成的密文。
优化建议(可选)
如果需要更高的兼容性和可控性,建议两端都放弃默认的密钥派生逻辑,显式指定固定长度的AES密钥(AES-256对应32字节密钥)和IV(固定16字节),避免因派生逻辑不一致导致的跨端适配问题:
- 密钥直接用32字节的随机字符串/二进制数据,不要用短文本直接作为密钥
- IV每次加密随机生成,和密文一起传输,解密时从密文指定位置提取即可
内容的提问来源于stack exchange,提问作者Aakash Rajput
相关产品推荐
相关产品推荐

