Flutter与JavaScript实现等效AES加密的完整指南
在跨平台应用开发中,经常需要在前端(如基于JavaScript的Web页面)与移动端(如Flutter应用)之间安全地传输数据。AES(高级加密标准)是对称加密的常用选择。然而,不同语言和平台的加密库在默认参数、填充方式、密钥处理等方面存在差异,直接调用很可能导致加密结果不一致。本文将详细讲解如何在JavaScript(使用CryptoJS)和Flutter(使用encrypt包)中实现完全等效的AES加密,确保两端能互相解密彼此的数据。
AES加密的关键参数
要实现跨平台的等效加密,必须对齐以下核心参数:
- 加密模式:如CBC(密码分组链接模式)、ECB(电码本模式)等。CBC模式需要初始向量(IV),更为安全。
- 填充方式:明文长度必须是块大小的整数倍(AES块大小为128位),不足时需要填充。PKCS7是最常用的填充标准。
- 密钥长度:AES支持128、192或256位密钥。通常使用128位(16字节)或256位(32字节)。
- 密钥与IV的编码:密钥和IV通常以字节数组形式使用,在传递时常编码为Base64或十六进制字符串。
- 输出格式:密文输出一般使用Base64编码以方便传输。
本文以最常用的CBC模式、PKCS7填充、128位密钥为例,演示如何在JavaScript和Flutter中得到完全相同的加密结果。
JavaScript端的实现(CryptoJS)
CryptoJS是一个被广泛使用的JavaScript加密库。使用前需引入CryptoJS库,可通过CDN或npm安装。以下示例演示了AES-CBC加密,密钥和IV使用CryptoJS的enc.Utf8.parse转换为WordArray,最后密文以Base64输出。
// 引入CryptoJS(此处假设通过CDN引入,实际项目中请使用对应方式)
// <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
function aesEncrypt(plainText, keyString, ivString) {
// 将密钥和IV字符串解析为WordArray(使用UTF8编码)
const key = CryptoJS.enc.Utf8.parse(keyString);
const iv = CryptoJS.enc.Utf8.parse(ivString);
// 加密配置:CBC模式,PKCS7填充
const encrypted = CryptoJS.AES.encrypt(plainText, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回Base64格式的密文
return encrypted.toString();
}
// 示例用法(密钥16字节,IV16字节)
const key16 = '1234567890123456'; // 128位密钥
const iv16 = 'abcdefghijklmnop'; // 128位IV
const originalText = 'Hello, Flutter!';
const cipherText = aesEncrypt(originalText, key16, iv16);
console.log('密文:', cipherText);上述代码中,密钥和IV都是精确16个字符(对应16字节)。如果密钥长度不足,CryptoJS会自动处理,但为了精确控制,我们应传递准确长度的字符串。解密时使用CryptoJS.AES.decrypt,并指定相同的模式、填充和IV即可还原明文。
Flutter端的实现(使用encrypt包)
在Flutter中,推荐使用encrypt包,它基于pointycastle实现了常见的加密算法。首先在pubspec.yaml中添加依赖:
dependencies: encrypt: ^5.0.3 # 请使用最新版本
然后实现加密函数,注意我们将密钥和IV字符串直接转换为字节数组(utf8.encode),这与JavaScript的Utf8.parse行为一致。Dart中字符串默认使用UTF-8编码。
import 'dart:convert';
import 'package:encrypt/encrypt.dart' as encrypt;
String aesEncryptFlutter(String plainText, String keyString, String ivString) {
// 将密钥和IV字符串转为UTF8字节数组
final key = encrypt.Key.fromUtf8(keyString);
final iv = encrypt.IV.fromUtf8(ivString);
// 创建AES加密器,指定CBC模式
final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
// 加密,Pkcs7填充是默认选项
final encrypted = encrypter.encrypt(plainText, iv: iv);
// 返回Base64密文
return encrypted.base64;
}
// 使用示例
void main() {
const key16 = '1234567890123456'; // 16字节
const iv16 = 'abcdefghijklmnop'; // 16字节
const original = 'Hello, Flutter!';
final cipher = aesEncryptFlutter(original, key16, iv16);
print('密文: $cipher');
}解密时使用encrypter.decrypt64(cipherBase64, iv: iv),即可得到原始明文。
对齐关键细节,确保加密结果完全一致
为了验证JavaScript和Flutter产生的密文是否相同,可以将两端生成的Base64密文进行比对。在上述示例中,使用相同的密钥、IV、明文和模式,输出应当完全匹配。以下是可能影响一致性的因素及解决办法:
| 因素 | JavaScript (CryptoJS) | Flutter (encrypt包) | 对齐方法 |
|---|---|---|---|
| 密钥处理 | CryptoJS.enc.Utf8.parse(keyString) 将字符串转为WordArray。 | Key.fromUtf8(keyString) 内部调用 utf8.encode 转为字节列表。 | 两者均基于UTF-8编码,结果一致。请确保密钥字符串长度对应期望的密钥长度(如16/24/32字节)。 |
| IV处理 | CryptoJS.enc.Utf8.parse(ivString) | IV.fromUtf8(ivString) | 同密钥处理,需保证IV长度恰好为16字节。 |
| 加密模式 | mode: CryptoJS.mode.CBC(默认也是CBC) | AES(key, mode: AESMode.cbc)(不指定时默认CBC) | 显式指定CBC以避免默认值变更。 |
| 填充 | padding: CryptoJS.pad.Pkcs7(默认) | encrypt包默认使用PKCS7,无需额外配置。 | 两者默认均为PKCS7,保持即可。 |
| 输出格式 | encrypted.toString() 返回Base64字符串。 | encrypted.base64 属性返回Base64字符串。 | 无需额外处理。 |
实际开发中,如果密钥或IV来自外部输入(如用户密码),建议使用标准的密钥派生函数(如PBKDF2)从密码生成固定长度的密钥,而不是直接将字符串作为密钥,以增强安全性。此时也需要保证两端使用相同的派生参数。
解密示例
为了完整展示,下面给出两端的解密代码。解密时需要使用与加密完全相同的密钥、IV和模式。
JavaScript解密:
function aesDecrypt(cipherBase64, keyString, ivString) {
const key = CryptoJS.enc.Utf8.parse(keyString);
const iv = CryptoJS.enc.Utf8.parse(ivString);
const decrypted = CryptoJS.AES.decrypt(cipherBase64, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回UTF8格式的明文
return decrypted.toString(CryptoJS.enc.Utf8);
}Flutter解密:
String aesDecryptFlutter(String cipherBase64, String keyString, String ivString) {
final key = encrypt.Key.fromUtf8(keyString);
final iv = encrypt.IV.fromUtf8(ivString);
final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
// 解密,返回原始文本
final decrypted = encrypter.decrypt64(cipherBase64, iv: iv);
return decrypted;
}测试验证
使用相同的明文、密钥和IV,分别在JavaScript控制台和Dart的main函数中运行上述加密函数,比较Base64输出。如果完全一致,说明加密等效;反之则需逐步检查参数。常见错误包括密钥字符串末尾带有空格、IV长度不对、编码方式不同等。
总结
实现Flutter与JavaScript之间的AES加密互通,关键在于统一加密参数:明确指定CBC模式、PKCS7填充,使用相同的密钥和IV字符串,并确保二者在转换为字节时的编码(UTF-8)一致。借助encrypt和CryptoJS库,可以快速编写出等效的加解密代码,满足前后端数据安全传输的需求。