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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:54:22