Flutter中使用encrypt包实现Rijndael/AES加密未得到预期结果
Flutter平台Rijndael加密实现方案与问题排查
Flutter生态有成熟的Rijndael加密实现可直接使用。注意AES是Rijndael算法在分组长度固定为128位时的标准化子集:如果对接场景使用标准AES参数(128/192/256位密钥、128位分组、CBC/ECB等常用模式+PKCS7填充),encrypt库即可满足需求;如果对接老系统使用非标准Rijndael参数(如192/256位分组长度),可直接基于底层密码库pointycastle的Rijndael核心实现自定义逻辑。
现有代码加密结果不匹配的核心原因
- 代码语法错误:你当前代码中
padding: "PKCS7"写法有误,encrypt库的AES构造函数要求padding参数传入Padding类型实例,而非字符串,该写法会导致填充逻辑不生效,默认填充行为与预期不符。正确写法应为padding: PKCS7Padding()。 - 密钥/IV编码错误:你使用
Key.fromUtf8()、IV.fromUtf8()将密钥、IV字符串按UTF8编码转字节,这是最常见的不匹配原因。绝大多数业务加密场景下,对接方提供的Key、IV为十六进制字符串或Base64字符串,并非明文UTF8字符串,用UTF8解码得到的字节数组与预期完全不符,会直接导致加密结果错误。需根据Key/IV的实际格式选择对应转换方法:- 十六进制格式Key/IV:使用
Key.fromBase16()、IV.fromBase16()转换 - Base64格式Key/IV:使用
Key.fromBase64()、IV.fromBase64()转换 - 仅当Key/IV本身为UTF8明文字符串、且转字节后长度严格为16/24/32位时,才使用
fromUtf8()转换
- 十六进制格式Key/IV:使用
- 明文格式不匹配:CBC模式下只要明文字节存在1位差异,从差异位置开始的后续所有密文块都会完全不同。你直接传入JSON对象序列化结果加密,需确认:
- JSON序列化规则与对接方完全一致:包括键值对顺序、数字类型序列化格式(如整数0是否会被序列化为0.0)、null值处理逻辑、是否存在多余空格/换行/转义字符
- 加密前是否需要对明文做额外处理(如Base64编码、拼接固定前后缀),部分业务接口会有自定义明文规则
- 算法参数不匹配:
encrypt库封装的AES实现仅支持128位分组的标准AES,如果对接方使用的是192/256位分组的原生Rijndael算法,该库无法支持,需切换到底层pointycastle实现。
正确实现代码示例
标准AES(Rijndael-128)CBC+PKCS7实现
首先在pubspec.yaml添加依赖:
dependencies: encrypt: ^5.0.3
加密方法实现:
import 'dart:convert'; import 'package:encrypt/encrypt.dart'; String encrypterAESdata({ required String data, required String aesKey, required String aesIv, // 传入key/iv的格式:utf8/base16/base64 String keyFormat = 'utf8', String ivFormat = 'utf8', }){ // 根据格式转换Key late final Key key; switch(keyFormat){ case 'base16': key = Key.fromBase16(aesKey); break; case 'base64': key = Key.fromBase64(aesKey); break; default: key = Key.fromUtf8(aesKey); } // 根据格式转换IV late final IV iv; switch(ivFormat){ case 'base16': iv = IV.fromBase16(aesIv); break; case 'base64': iv = IV.fromBase64(aesIv); break; default: iv = IV.fromUtf8(aesIv); } // 正确传入PKCS7Padding实例 final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: PKCS7Padding())); final encrypted = encrypter.encrypt(data, iv: iv); return encrypted.base64; }
使用时需注意,序列化JSON要固定规则,避免格式差异:
// 固定JSON序列化规则,避免键顺序、类型转换导致的明文差异 final plainText = jsonEncode(yourJsonObject, toEncodable: (dynamic obj) => obj?.toString()); // 调用加密方法,根据实际key/iv格式传入对应format参数 final encryptedResult = encrypterAESdata( data: plainText, aesKey: yourAesKey, aesIv: yourAesIv, keyFormat: 'base64', // 替换为实际key格式 ivFormat: 'base64', // 替换为实际iv格式 );
非标准分组长度Rijndael实现
如果对接场景使用192/256位分组的原生Rijndael,使用pointycastle实现,添加依赖:
dependencies: pointycastle: ^3.7.3
加密实现:
import 'dart:typed_data'; import 'package:pointycastle/export.dart'; /// Rijndael加密 /// [blockSize] 分组长度,可选16(128位)/24(192位)/32(256位) Uint8List rijndaelCbcEncrypt( Uint8List plainText, Uint8List key, Uint8List iv, int blockSize, ) { // 初始化CBC模式Rijndael加密器 final cipher = CBCBlockCipher(RijndaelEngine(blockSize)) ..init(true, ParametersWithIV(KeyParameter(key), iv)); // PKCS7填充 final paddedText = _pkcs7Pad(plainText, blockSize); // 分块加密 final output = Uint8List(paddedText.length); int offset = 0; while (offset < paddedText.length) { offset += cipher.processBlock(paddedText, offset, output, offset); } return output; } /// PKCS7填充逻辑 Uint8List _pkcs7Pad(Uint8List data, int blockSize) { final padLen = blockSize - (data.length % blockSize); final padded = Uint8List(data.length + padLen); padded.setAll(0, data); padded.fillRange(data.length, padded.length, padLen); return padded; }
排查顺序建议
- 先校验Key、IV字节:找对接方索要可运行的其他语言(Java/PHP/JS)加密示例,将本地转换得到的Key、IV字节数组与示例中的字节数组对比,确保完全一致。
- 用简单明文验证:使用固定短明文(如
test123)、确定的Key和IV加密,先验证简单场景下密文是否匹配,避免长JSON的格式问题干扰排查。 - 最后校验JSON格式:简单明文验证通过后,再逐字符对比序列化后的JSON字符串与对接方加密用的明文,重点排查键顺序、数字格式、空值、空格换行等细节。
内容的提问来源于stack exchange,提问作者MD MEHEDI HASAN
相关产品推荐
相关产品推荐

