Flutter:ECIES 加密/解密

问题描述 投票:0回答:1

我正在开发一款移动应用程序,为了安全起见,它已经实施了 ECIES 方案来加密信息。根据我所做的研究,ECIES 的实施是使用 KDF2。

我收到字符串形式的密钥对,这些应该用于加密或解密。

我已经搜索过信息,但没有找到太多。我有这个 JavaScript 示例,它可以满足我在 Flutter 中的需要。

import { encrypt, decrypt } from 'eciesjs';

class ECIES {
    private_key;
    public_key;

    constructor(private_key = '', public_key = '') {
        this.private_key = private_key;
        this.public_key = public_key;
    }

    encrypt(message = '') {
        try {
            if (this.public_key === '') {
                throw new Error("Can't encrypt your data, 'public_key' is not defined.");
            }

            let key_buffered = Buffer.from(this.public_key, 'base64').toString();
            let encrypted = encrypt(key_buffered, message);

            return Buffer.from(encrypted).toString('base64');

        } catch(e) {
            console.log(`Error: ${e}`);
            throw e;
        }
    }

    decrypt(data) {
        try {
            if (this.private_key === '') {
                throw new Error("Can't decrypt your data, 'private_key' is not defined.");
            }
            
            let key_buffered = Buffer.from(this.private_key, 'base64').toString();
            let decrypted = decrypt(key_buffered, Buffer.from(data, 'base64'));

            return Buffer.from(decrypted).toString('utf-8');

        } catch(e) {
            console.log(`Error: ${e}`);
            throw e;
        }
    }

    getKeyPair() {
        return {
            "private_key": this.private_key,
            "public_key": this.public_key
        };
    }
}


export { ECIES };
import { ECIES } from './ecies.js';


function test() {
    const private_key = "MDllOTFkYjMxZTNiNTYwMzdkOTVlOGQxYmEyYjQ3NzhjN2M5MGNlODE4YWI0MDE4NWE2YTZiNTQ1MTRmOGM1Zg=="
    const public_key = "MDIzN2E0M2RhYWJiZDJjMjJhZmVjYzE3ZWU3MDkxMDQ1ZDU1YzBkODg2ODIxYmYwMTA0YjEyM2Y0ZmRlZWMyMjc5"
    const ecies = new ECIES(private_key, public_key);

    const message = "Hello world!";

    let msg_encrypted = ecies.encrypt(message);
    console.log(`Encrypted Message: ${msg_encrypted}`);

    let msg_decrypted = ecies.decrypt(msg_encrypted);
    console.log(`Decrypted Message: ${msg_decrypted}`);

    const data = "BD2NPMycdxfE2hJB5jyG6ozs7MHOA0hQrsrEeq5hnLs9PkZmNQE46BAzrO2dUZ0ecKsT2rB6PZo6jzIEU2b0kimhyV29eE6y0E4" +
        "hVbdq14RwVXjnAhSODN8ZC5RBxsjp31ivqH0zAKHMpfHRiPkBBPgVr1gPurSvkkNMknXUtYtPBxbQc9IHpIlZe8YQWX105obraACxDOoCHV2" +
        "I1kWUiuxlABI1knO0pD1e9mNwmdgkq5YhJApVKKVX4WUcGrfVHNnvdRTkBXCf";

    let data_dec = ecies.decrypt(data);
    console.log(`Decrypted Message: ${data_dec}`);
}


(() => {
    test();
})();
flutter dart security encryption
1个回答
0
投票

JavaScript 代码属于一个suite,它为各种平台实现兼容的 ECIES 代码,包括使用 BouncyCastle 库的Java 代码

对于 Dart 有一个 BouncyCastle 端口,即 PointyCastle。因此,Java 实现可以用作 Dart 实现的模板。

关于加密,需要以下步骤:

  1. 创建临时密钥对
  2. 导入接收方公钥
  3. 获取 ECDH 共享密钥的完整点,通过 HKDF 派生密钥
  4. 通过 AES-256、GCM 加密
  5. 连接(ephPublicKeyUncomp|nonce|tag|ciphertext)和Base64编码
import 'dart:math';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:pointycastle/export.dart';

...

var rnd = getSecureRandom();
var domainParams = ECDomainParameters("secp256k1");

String encrypt(var rawPubKeyCompUncomp, var plaintext){

  // 1. Create ephemeral key pair
  var ephKeyPair = (KeyGenerator("EC")..init(ParametersWithRandom(ECKeyGeneratorParameters(domainParams), rnd))).generateKeyPair();

  // 2. Import public key of receiver
  var ecPoint = domainParams.curve.decodePoint(hex.decode(rawPubKeyCompUncomp));
  var publicKey = ECPublicKey(ecPoint, domainParams);

  // 3. Get full point to ECDH shared secret, derive key via HKDF
  var sharedSecretECPointUncomp = (publicKey.Q! * (ephKeyPair.privateKey as ECPrivateKey).d)!.getEncoded(false);
  var ephPublicKeyUncomp = (ephKeyPair.publicKey as ECPublicKey).Q!.getEncoded(false);
  var aesKey = hkdf(ephPublicKeyUncomp, sharedSecretECPointUncomp);

  // 4. Encrypt via AES-256, GCM
  var nonce = rnd.nextBytes(16);
  var ciphertextTag = (GCMBlockCipher(AESEngine())..init(true, AEADParameters(KeyParameter(aesKey), 128, nonce, Uint8List(0)))).process(plaintext);

  // 5. Concatenate (ephPublicKeyUncomp|nonce|tag|ciphertext), Base64 encode and return
  return base64.encode(ephPublicKeyUncomp + nonce + ciphertextTag.sublist(ciphertextTag.length - 16) + ciphertextTag.sublist(0, ciphertextTag.length - 16));
}

SecureRandom getSecureRandom() {
  List<int> seed = List<int>.generate(32, (_) => Random.secure().nextInt(256));
  return FortunaRandom()..seed(KeyParameter(Uint8List.fromList(seed)));
}

Uint8List hkdf(var ephPublicKeyUnc, var sharedSecretEcPointUnc) {
  var master = Uint8List.fromList(ephPublicKeyUnc + sharedSecretEcPointUnc);
  var aesKey = Uint8List(32);
  (HKDFKeyDerivator(SHA256Digest())..init(HkdfParameters(master, 32, null))).deriveKey(null, 0, aesKey, 0);
  return aesKey;
}

关于解密:

  1. 导入私钥
  2. 分离临时公钥和nonce|tag|密文,导入临时公钥
  3. 获取 ECDH 共享密钥的完整点,通过 HKDF 派生密钥
  4. 通过 AES-256、GCM 解密
  5. UTF-8解码
String decrypt(var rawPrivKeyHex, var encryptedDataB64){

  // 1. Import private key
  var privateKey = ECPrivateKey(BigInt.parse(rawPrivKeyHex, radix: 16), domainParams);

  // 2. Separate ephemeral public key and nonce|tag|ciphertext, import ephemeral public key
  var encryptedData = base64.decode(encryptedDataB64);
  var ephPublicKey = ECPublicKey(domainParams.curve.decodePoint(encryptedData.sublist(0, 65)), domainParams);
  var nonceTagCiphertext = encryptedData.sublist(65);

  // 3. Get full point to ECDH shared secret, derive key via HKDF
  var sharedSecretECPointUncomp = (ephPublicKey.Q! * privateKey.d)!.getEncoded(false);
  var ephPublicKeyUncomp = ephPublicKey.Q!.getEncoded(false);
  var aesKey = hkdf(ephPublicKeyUncomp, sharedSecretECPointUncomp);

  // 4. Decrypt via AES-256, GCM
  var nonce = nonceTagCiphertext.sublist(0, 16);
  var ciphertextTag = nonceTagCiphertext.sublist(16 + 16) + nonceTagCiphertext.sublist(16, 16 + 16);
  var plaintext = (GCMBlockCipher(AESEngine())..init(false, AEADParameters(KeyParameter(aesKey), 128, nonce, Uint8List(0)))).process(Uint8List.fromList(ciphertextTag));

  // 5. UTF-8 decode and return
  return utf8.decode(plaintext);
}

测试一:加解密:

var rawPubKeyHex = "0237a43daabbd2c22afecc17ee7091045d55c0d886821bf0104b123f4fdeec2279";
var rawPrivKeyHex = "09e91db31e3b56037d95e8d1ba2b4778c7c90ce818ab40185a6a6b54514f8c5f";

var plaintext = utf8.encode("Es genial trabajar con ordenadores. No discuten, lo recuerdan todo y no se beben tu cerveza. -Paul Leary");
var encryptedDataB64 = encrypt(rawPubKeyHex, plaintext);
print(encryptedDataB64);

var decryptedText = decrypt(rawPrivKeyHex, encryptedDataB64);
print(decryptedText); // Es genial trabajar con ordenadores. No discuten, lo recuerdan todo y no se beben tu cerveza. -Paul Leary

测试二:JavaScript密文解密(证明与JavaScript代码的兼容性):

var rawPrivKeyHex = "09e91db31e3b56037d95e8d1ba2b4778c7c90ce818ab40185a6a6b54514f8c5f";

var encryptedDataB64 = "BD2NPMycdxfE2hJB5jyG6ozs7MHOA0hQrsrEeq5hnLs9PkZmNQE46BAzrO2dUZ0ecKsT2rB6PZo6jzIEU2b0kimhyV29eE6y0E4hVbdq14RwVXjnAhSODN8ZC5RBxsjp31ivqH0zAKHMpfHRiPkBBPgVr1gPurSvkkNMknXUtYtPBxbQc9IHpIlZe8YQWX105obraACxDOoCHV2I1kWUiuxlABI1knO0pD1e9mNwmdgkq5YhJApVKKVX4WUcGrfVHNnvdRTkBXCf";
var decryptedText = decrypt(rawPrivKeyHex, encryptedDataB64);
print(decryptedText); // Es genial trabajar con ordenadores. No discuten, lo recuerdan todo y no se beben tu cerveza. -Paul Leary

请注意,您发布的密钥经过两次编码,一次是十六进制编码,然后是 Base64 编码,例如

的Base64解码
MDllOTFkYjMxZTNiNTYwMzdkOTVlOGQxYmEyYjQ3NzhjN2M5MGNlODE4YWI0MDE4NWE2YTZiNTQ1MTRmOGM1Zg== 

随后的 ASCII 解码给出

09e91db31e3b56037d95e8d1ba2b4778c7c90ce818ab40185a6a6b54514f8c5f

这种双重编码毫无意义且效率低下。因此,在上面的例子中,省略了Base64编码,直接使用了十六进制编码的密钥。

另请注意,对于 GCM,ecies 库应用 16 字节的随机数长度,而不是推荐的 12 字节长度。上面的代码也使用了 16 个字节来兼容 JavaScript 代码。

© www.soinside.com 2019 - 2024. All rights reserved.