Node.js与React Native AES加解密遇Malformed UTF-8 data错误
解决Node.js与React Native AES加解密的"Malformed UTF-8 data"错误
核心问题分析
你的加解密逻辑存在两个关键不一致点,直接导致了解密失败:
- 密钥生成不匹配:服务端用
crypto.scryptSync从密码和盐生成了32字节的AES-256密钥,但客户端直接将原密码转成UTF-8字节作为密钥。两者的密钥完全不同,解密后必然得到乱码,无法解析为合法UTF-8数据。 - 密文格式处理错误:CryptoJS的
AES.decrypt默认将输入的字符串视为Base64编码,但服务端输出的是十六进制密文,直接传入会导致解码错误。
修正方案(推荐:保持scrypt密钥生成,更安全)
服务端逻辑无需修改,客户端需要同步使用scrypt生成相同密钥,并正确处理十六进制密文:
1. 安装依赖
首先安装React Native版的scrypt库:
npm install react-native-scrypt # 或 yarn add react-native-scrypt
2. 修改客户端解密函数
import CryptoJS from 'react-native-crypto-js'; import scrypt from 'react-native-scrypt'; async function decryptGameData(encryptedText, ivHex) { const password = "TEST123"; const salt = 'salt'; // 必须和服务端的盐完全一致 // 生成与服务端相同的32字节密钥 const keyBuffer = await scrypt(password, salt, 32, { N: 16384, r: 8, p: 1 }); const key = CryptoJS.lib.WordArray.create(keyBuffer); const iv = CryptoJS.enc.Hex.parse(ivHex); // 将十六进制密文转换为CryptoJS可识别的WordArray const ciphertext = CryptoJS.enc.Hex.parse(encryptedText); const decrypted = CryptoJS.AES.decrypt( { ciphertext: ciphertext }, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 // 显式指定填充,与Node.js crypto默认一致 } ); return decrypted.toString(CryptoJS.enc.Utf8); }
简化方案(仅用于测试,不推荐生产环境)
如果不想引入额外依赖,可以统一去掉scrypt,直接使用密码的UTF-8字节作为密钥(注意AES密钥长度要求:128位=16字节,256位=32字节):
服务端修改后的加密函数
const crypto = require('crypto'); function encrypt(text) { const password = "TEST123"; // 直接用密码的UTF-8字节,补零到32字节用于AES-256 const key = Buffer.alloc(32); Buffer.from(password, 'utf8').copy(key); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return { gameId: iv.toString('hex'), gameData: encrypted }; }
客户端修改后的解密函数
import CryptoJS from 'react-native-crypto-js'; function decryptGameData(encryptedText, ivHex) { const password = "TEST123"; // 生成与服务端一致的32字节密钥(补零) const keyWordArray = CryptoJS.enc.Utf8.parse(password); const key = CryptoJS.lib.WordArray.create(32); keyWordArray.copy(key); const iv = CryptoJS.enc.Hex.parse(ivHex); const ciphertext = CryptoJS.enc.Hex.parse(encryptedText); const decrypted = CryptoJS.AES.decrypt( { ciphertext: ciphertext }, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); return decrypted.toString(CryptoJS.enc.Utf8); }
关键注意事项
- 盐值(
'salt')必须在前后端完全一致,生产环境建议使用随机盐并随密文一起传输,而非硬编码。 - AES模式、填充方式必须两端统一,Node.js crypto和CryptoJS默认均为PKCS#7填充,显式指定可避免兼容问题。
- 生产环境请勿硬编码密码,建议使用环境变量管理敏感信息。
内容的提问来源于stack exchange,提问作者Sunil Meena
相关产品推荐
相关产品推荐

