Flutter中实现ECIES加解密:适配KDF2方案与字符串密钥对
Flutter实现基于KDF2的ECIES加解密(兼容eciesjs)
针对你需要在Flutter中实现和提供的JavaScript代码同等功能的ECIES加解密需求,下面是完整的实现方案,依赖pointycastle(Flutter生态常用的加密库)来实现基于KDF2的ECIES逻辑,完全对齐eciesjs的默认配置(secp256k1曲线、SHA256哈希、AES-256-CBC加密、KDF2密钥派生)。
1. 添加依赖
在pubspec.yaml中加入以下依赖:
dependencies: pointycastle: ^3.7.3 convert: ^3.1.1
2. ECIES工具类实现
import 'dart:convert'; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/ecc/api.dart'; import 'package:pointycastle/ecc/curves/secp256k1.dart'; import 'package:pointycastle/key_derivators/kdf2.dart'; import 'package:pointycastle/block/aes_fast.dart'; import 'package:pointycastle/paddings/pkcs7.dart'; import 'package:pointycastle/modes/cbc.dart'; import 'package:pointycastle/digests/sha256.dart'; import 'package:convert/convert.dart'; class ECIES { String? privateKey; String? publicKey; final ECCurve _curve = ECCurve_secp256k1(); ECIES([this.privateKey = '', this.publicKey = '']); /// 加密明文,返回Base64编码的密文 String encrypt(String message) { if (publicKey == null || publicKey!.isEmpty) { throw Exception("无法加密数据,'public_key'未定义"); } // 解码Base64公钥为hex字符串,再转换为EC公钥对象 final publicKeyHex = utf8.decode(base64.decode(publicKey!)); final pubKeyPoint = _curve.decodePoint(hex.decode(publicKeyHex))!; // 生成临时密钥对 final keyGenParams = ECKeyGeneratorParameters(_curve); final generator = ECKeyGenerator(); generator.init(ParametersWithRandom(keyGenParams, SecureRandom('Fortuna'))); final ephemeralKeyPair = generator.generateKeyPair(); final ephemeralPrivKey = ephemeralKeyPair.privateKey as ECPrivateKey; final ephemeralPubKey = ephemeralKeyPair.publicKey as ECPublicKey; // 计算共享密钥 final sharedSecret = ephemeralPrivKey.d! * pubKeyPoint.Q!; final sharedSecretBytes = sharedSecret.getEncoded(false); // KDF2派生对称密钥(AES-256需要32字节) final kdf = KDF2Derivator(SHA256Digest()); kdf.init(Pbkdf2Parameters(sharedSecretBytes, Uint8List(0), 1)); final symmetricKey = kdf.process(Uint8List(32)); // 生成AES-CBC初始化向量(16字节) final iv = SecureRandom('Fortuna').nextBytes(16); // AES-CBC加密(PKCS7填充) final cipher = CBCBlockCipher(AESFastEngine()); cipher.init(true, ParametersWithIV(KeyParameter(symmetricKey), iv)); final paddedData = PKCS7Padding().addPadding(utf8.encode(message), 0); final encryptedData = cipher.process(Uint8List.fromList(paddedData)); // 拼接结果:临时公钥(65字节) + IV(16字节) + 密文 final ephemeralPubKeyBytes = ephemeralPubKey.Q!.getEncoded(false); final resultBytes = Uint8List.fromList([ ...ephemeralPubKeyBytes, ...iv, ...encryptedData, ]); return base64.encode(resultBytes); } /// 解密Base64编码的密文,返回明文 String decrypt(String data) { if (privateKey == null || privateKey!.isEmpty) { throw Exception("无法解密数据,'private_key'未定义"); } // 解码Base64密文为字节数组 final dataBytes = base64.decode(data); // 拆分数据:临时公钥(前65字节)、IV(接下来16字节)、密文(剩余部分) final ephemeralPubKeyBytes = dataBytes.sublist(0, 65); final iv = dataBytes.sublist(65, 65 + 16); final encryptedData = dataBytes.sublist(65 + 16); // 解码Base64私钥为hex字符串,转换为EC私钥对象 final privateKeyHex = utf8.decode(base64.decode(privateKey!)); final d = BigInt.parse(privateKeyHex, radix: 16); final privKey = ECPrivateKey(d, _curve); // 解析临时公钥 final ephemeralPubKeyPoint = _curve.decodePoint(ephemeralPubKeyBytes)!; // 计算共享密钥 final sharedSecret = d * ephemeralPubKeyPoint.Q!; final sharedSecretBytes = sharedSecret.getEncoded(false); // KDF2派生对称密钥 final kdf = KDF2Derivator(SHA256Digest()); kdf.init(Pbkdf2Parameters(sharedSecretBytes, Uint8List(0), 1)); final symmetricKey = kdf.process(Uint8List(32)); // AES-CBC解密,去除PKCS7填充 final cipher = CBCBlockCipher(AESFastEngine()); cipher.init(false, ParametersWithIV(KeyParameter(symmetricKey), iv)); final decryptedPadded = cipher.process(encryptedData); final paddingLength = decryptedPadded.last; final decryptedData = decryptedPadded.sublist(0, decryptedPadded.length - paddingLength); return utf8.decode(decryptedData); } /// 获取当前密钥对 Map<String, String> getKeyPair() { return { 'private_key': privateKey ?? '', 'public_key': publicKey ?? '', }; } }
3. 测试代码(对应JS示例)
void testECIES() { const privateKey = "MDllOTFkYjMxZTNiNTYwMzdkOTVlOGQxYmEyYjQ3NzhjN2M5MGNlODE4YWI0MDE4NWE2YTZiNTQ1MTRmOGM1Zg=="; const publicKey = "MDIzN2E0M2RhYWJiZDJjMjJhZmVjYzE3ZWU3MDkxMDQ1ZDU1YzBkODg2ODIxYmYwMTA0YjEyM2Y0ZmRlZWMyMjc5"; final ecies = ECIES(privateKey, publicKey); const message = "Hello world!"; final encrypted = ecies.encrypt(message); print("加密后的消息: $encrypted"); final decrypted = ecies.decrypt(encrypted); print("解密后的消息: $decrypted"); const testData = "BD2NPMycdxfE2hJB5jyG6ozs7MHOA0hQrsrEeq5hnLs9PkZmNQE46BAzrO2dUZ0ecKsT2rB6PZo6jzIEU2b0kimhyV29eE6y0E4hVbdq14RwVXjnAhSODN8ZC5RBxsjp31ivqH0zAKHMpfHRiPkBBPgVr1gPurSvkkNMknXUtYtPBxbQc9IHpIlZe8YQWX105obraACxDOoCHV2I1kWUiuxlABI1knO0pD1e9mNwmdgkq5YhJApVKKVX4WUcGrfVHNnvdRTkBXCf"; final decryptedTestData = ecies.decrypt(testData); print("解密的测试数据: $decryptedTestData"); }
关键对齐说明
- 曲线选择:使用secp256k1,和eciesjs默认配置一致
- 密钥派生:采用KDF2-SHA256,迭代次数1、空盐,完全匹配JS库逻辑
- 对称加密:AES-256-CBC模式+PKCS7填充,和eciesjs的加密方式对齐
- 密钥处理:兼容Base64编码的hex格式密钥,和JS代码的密钥解析逻辑一致
内容的提问来源于stack exchange,提问作者Erick
相关产品推荐
相关产品推荐

