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

Node.js与React Native AES加解密遇Malformed UTF-8 data错误

解决Node.js与React Native AES加解密的"Malformed UTF-8 data"错误

核心问题分析

你的加解密逻辑存在两个关键不一致点,直接导致了解密失败:

  1. 密钥生成不匹配:服务端用crypto.scryptSync从密码和盐生成了32字节的AES-256密钥,但客户端直接将原密码转成UTF-8字节作为密钥。两者的密钥完全不同,解密后必然得到乱码,无法解析为合法UTF-8数据。
  2. 密文格式处理错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 10:04:59