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

如何在Flutter/Dart中解密由CryptoJS加密的文本

Flutter(Dart) 实现 CryptoJS AES 解密问题

我们有一段用于消息加解密的CryptoJS代码,需要在Flutter(Dart)中实现对应的解密功能:

/* 加密文本
 * @param text
 */
export const encrypt = (text: string): string => {
  const encJson = CryptoJS.AES.encrypt(JSON.stringify(text), SECRET_KEY).toString();
  return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encJson));
};

/* 解密文本
 * @param ciphertext
 */
export const decrypt = (ciphertext: string): string => {
  const decData = CryptoJS.enc.Base64.parse(ciphertext).toString(CryptoJS.enc.Utf8);
  const bytes = CryptoJS.AES.decrypt(decData, SECRET_KEY).toString(CryptoJS.enc.Utf8);
  return JSON.parse(bytes);
};

我尝试了相关示例但未能成功,以下是当前的Dart代码,恳请帮忙指出需要修改的地方:

String decryptAESCryptoJS(String encrypted, String passphrase) {
  try {
    Uint8List encryptedBytesWithSalt = base64.decode(encrypted);

    Uint8List encryptedBytes =
        encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
    final salt = encryptedBytesWithSalt.sublist(8, 16);
    var keyndIV = deriveKeyAndIV(passphrase, salt);
    final key = encrypt.Key(keyndIV.item1);
    final iv = encrypt.IV(keyndIV.item2);
    final encrypter = encrypt.Encrypter(
        encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
    final decrypted =
        encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
    return decrypted;
  } catch (error) {
    throw error;
  }
}

Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
  var password = createUint8ListFromString(passphrase);
  Uint8List concatenatedHashes = Uint8List(0);
  List<int> currentHash = Uint8List(0);
  bool enoughBytesForKey = false;
  Uint8List preHash = Uint8List(0);

  while (!enoughBytesForKey) {
    int preHashLength = currentHash.length + password.length + salt.length;
    if (currentHash.length > 0)
      preHash = Uint8List.fromList(currentHash + password + salt);
    else
      preHash = Uint8List.fromList(password + salt);

    currentHash = md5.convert(preHash).bytes;
    concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
    if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
  }

  var keyBtyes = concatenatedHashes.sublist(0, 32);
  var ivBtyes = concatenatedHashes.sublist(32, 48);
  return new Tuple2(keyBtyes, ivBtyes);
}

Uint8List createUint8ListFromString(String s) {
  var ret = new Uint8List(s.length);
  for (var i = 0; i < s.length; i++) {
    ret[i] = s.codeUnitAt(i);
  }
  return ret;
}

Uint8List genRandomWithNonZero(int seedLength) {
  final random = Random.secure();
  const int randomMax = 245;
  final Uint8List uint8list = Uint8List(seedLength);
  for (int i = 0; i < seedLength; i++) {
    uint8list[i] = random.nextInt(randomMax) + 1;
  }
  return uint8list;
}

问题分析与修改方案

你的代码存在两个核心问题,对应CryptoJS的默认行为匹配错误:

  • 密文解析逻辑错误:JS加密时是把CryptoJS.AES.encrypt的字符串结果转UTF8再Base64,所以解密第一步应该是Base64解码后转成UTF8字符串,而不是直接当成带Salt的二进制数据处理。
  • 密钥推导方式错误:当前CryptoJS(v3.x及以上)默认用PBKDF2算法生成密钥和IV,而你的代码用的是老版本的MD5迭代方式,两者不兼容。

修改后的完整Dart代码如下(需要依赖encrypt和pointycastle包):

import 'dart:convert';
import 'package:encrypt/encrypt.dart';
import 'package:pointycastle/pointycastle.dart';

String decryptAESCryptoJS(String encrypted, String passphrase) {
  try {
    // 1. 第一步:Base64解码得到UTF8字符串(对应JS里的decData)
    Uint8List base64Decoded = base64.decode(encrypted);
    String cryptoJsEncStr = utf8.decode(base64Decoded);

    // 2. 解析CryptoJS生成的加密字符串(格式为Salted__ + salt + ciphertext)
    Uint8List cryptoJsEncBytes = utf8.encode(cryptoJsEncStr);
    if (cryptoJsEncBytes.length < 16 || utf8.decode(cryptoJsEncBytes.sublist(0,8)) != 'Salted__') {
      throw Exception('无效的CryptoJS加密格式');
    }
    Uint8List salt = cryptoJsEncBytes.sublist(8, 16);
    Uint8List ciphertext = cryptoJsEncBytes.sublist(16);

    // 3. 用PBKDF2推导密钥和IV(匹配CryptoJS默认参数:迭代1000次,SHA-256,32位密钥+16位IV)
    var keyIv = _deriveKeyIvPBKDF2(passphrase, salt);
    final key = Key(keyIv.item1);
    final iv = IV(keyIv.item2);

    // 4. AES-CBC解密,PKCS7填充
    final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: 'PKCS7'));
    // 这里直接解密二进制数据,不需要再Base64编码
    String decryptedJson = encrypter.decrypt(Encrypted(ciphertext), iv: iv);
    
    // 5. 解析JSON得到原始明文
    return jsonDecode(decryptedJson);
  } catch (error) {
    throw error;
  }
}

Tuple2<Uint8List, Uint8List> _deriveKeyIvPBKDF2(String passphrase, Uint8List salt) {
  Pbkdf2Parameters params = Pbkdf2Parameters(salt, 1000, 32 + 16);
  KeyDerivator derivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
  derivator.init(params);
  
  Uint8List passwordBytes = utf8.encode(passphrase);
  Uint8List keyIvBytes = derivator.process(passwordBytes);
  
  Uint8List key = keyIvBytes.sublist(0, 32);
  Uint8List iv = keyIvBytes.sublist(32, 48);
  return Tuple2(key, iv);
}

// 辅助类:替代原来的Tuple2,或者直接使用tuple包的Tuple2
class Tuple2<A, B> {
  final A item1;
  final B item2;
  Tuple2(this.item1, this.item2);
}

修改说明

  • 密文解析:先把Base64解码后的字节转成UTF8字符串,再解析这个字符串的二进制内容,提取Salt和密文(CryptoJS的加密字符串默认带Salted__前缀)。
  • 密钥推导:改用PBKDF2算法,匹配CryptoJS的默认参数:迭代次数1000,哈希算法SHA-256,生成32位AES密钥和16位IV。
  • 解密逻辑:直接解密提取出的密文二进制数据,最后解析JSON得到原始明文。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 00:20:17