如何在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
相关产品推荐
相关产品推荐

