1

I‘m a rookie in PointyCastle,how can I generate ecc base64 key pairs with PointyCastle in dart

AsymmetricKeyPair<PublicKey, PrivateKey> generateKeyPair({curve = 'secp256r1'}) {
    var param = curve == 'secp256r1' ? ECCurve_secp256r1() : ECCurve_secp256k1();
    var keyParams = ECKeyGeneratorParameters(param);

    var random = FortunaRandom();
    random.seed(KeyParameter(_seed()));

    var generator = ECKeyGenerator();
    generator.init(ParametersWithRandom(keyParams, random));

    return generator.generateKeyPair();
}
Uint8List _seed() {
    var random = Random.secure();
    var seed = List<int>.generate(32, (_) => random.nextInt(256));
    return Uint8List.fromList(seed);
}

above I can generate a AsymmetricKeyPair object,how to get public base64 and private base64 like my already done js bundle

{"priKey":"CVK3r/UxdGCwQBjtn5vN/orUMxKf9E/1TlJzLkMz9t4=","pubKey":"BJH/mWJqgchGxXGA5/E79SsWRwVo3rpduBmD8FOs7UlKiK8PIvwrkCDvUcwhKdysW35OByPjoVcwFqg1NyumLKM="}

besides, at first I want to use my js bundle file in flutter android, but that would be very tough as I know

how to use js bundle in flutter android

I also need to make sign and verify function with ecdsa in sha256

Signer signer = new Signer('SHA-256/ECDSA');
// 签名,参数:私钥,明文
ECSignature sign(String privateKeyStr, String txt) {
    SecureRandom random = _setRadom();
    // how to convert a plain txt to kTestBytes
    final Uint8List kTestBytes = new Uint8List.fromList([1, 2, 3]);

    // if I pass in base64 privateKey str, is the radix 64?
    ECPrivateKey privateKey = new ECPrivateKey(BigInt.parse(privateKeyStr, radix: 10), ecDomain);

    PrivateKeyParameter signParams = new PrivateKeyParameter(privateKey);
    signer.init(true, new ParametersWithRandom(signParams, random));
    ECSignature signature = signer.generateSignature(kTestBytes) as ECSignature;
    return signature;
}
// 验签
//  参数: 明文,签名,公钥
bool verify(txt, signature, publicKey) {
    // how to make the txt to kTestBytes
    var kTestBytes = txt
    signer.init(false, new PublicKeyParameter(publicKey));
    bool verify = signer.verifySignature(kTestBytes, signature);
    // print(verify);
    return verify;
}
// I don't know what this function actually do,as I know in other language ecdsa don;t need a random number.
SecureRandom _setRadom() {
    List<int> key = [67, 3, 241, 75, 143, 78, 115, 99, 21, 242, 180, 43, 26, 7, 194, 20];
    List<int> iv = [87, 117, 137, 182, 2, 199, 132, 230, 120, 12, 109, 177, 34, 197, 186, 206];
    KeyParameter keyParam = new KeyParameter(new Uint8List.fromList(key));
    ParametersWithIV<KeyParameter> paramsIV = new ParametersWithIV(keyParam, new Uint8List.fromList(iv));
    SecureRandom random = new SecureRandom('AES/CTR/AUTO-SEED-PRNG')..seed(paramsIV);
    return random;
}

Any place can I find sample codes, these functions were changed by some copies on the internet。and I got many confuses list in the comment

John
  • 645
  • 1
  • 8
  • 23
  • *...as I know, ecdsa does not require randomness in other languages...*: The non-deterministic ECDSA algorithm requires randomness. Other platforms may use default implementations for the CSPRNG or they may use the deterministic variant. Regarding key import/export. What is the desired key format: raw, ASN.1/DER (e.g. X.509, PKCS#8, SEC#1), etc.? – Topaco Jun 16 '22 at 08:45
  • kind like this ```js {"priKey":"CVK3r/UxdGCwQBjtn5vN/orUMxKf9E/1TlJzLkMz9t4=","pubKey":"BJH/mWJqgchGxXGA5/E79SsWRwVo3rpduBmD8FOs7UlKiK8PIvwrkCDvUcwhKdysW35OByPjoVcwFqg1NyumLKM="} ``` – John Jun 16 '22 at 08:53
  • I have a js bundle,which has all the funcitons accepting base64 string params and return base64 strings – John Jun 16 '22 at 08:54
  • *base64 string params* is not a description of the key format. Base64 is an encoding and can be applied to various formats. What you' ve posted is the Base64 encoded *raw private* key and the Base64 encoded *uncompressed public* key. You should make that more precise in your question. – Topaco Jun 16 '22 at 09:02
  • ok ,I re-Edit my question – John Jun 16 '22 at 09:24

2 Answers2

3

The EC keys are to be exported/imported as a Base64 encoded raw private key and as a Base64 encoded raw uncompressed public key.

First of all it can be stated that signing and verifying with the posted code works when a fresh key pair is generated:

AsymmetricKeyPair<PublicKey, PrivateKey> kp = generateKeyPair();
ECSignature signature = sign(kp.privateKey as ECPrivateKey, "The quick brown fox jumps over the lazy dog");
bool verified = verify("The quick brown fox jumps over the lazy dog", signature, kp.publicKey as ECPublicKey);
print(verified); // true

I.e. in the following I will focus on the import/export of the keys. The raw keys are encapsulated in ECPrivateKey and ECPublicKey and can be exported and imported as follows:

String p256 = 'secp256r1';
AsymmetricKeyPair<PublicKey, PrivateKey> kp = generateKeyPair(curve: p256);

// Export
BigInt d = (kp.privateKey as ECPrivateKey).d!;
BigInt x = (kp.publicKey as ECPublicKey).Q!.x!.toBigInteger()!;
BigInt y = (kp.publicKey as ECPublicKey).Q!.y!.toBigInteger()!;

// Import
ECDomainParameters domain = ECDomainParameters(p256);
ECPrivateKey ecPrivateKey = ECPrivateKey(d, domain);
ECPublicKey ecPublicKey = ECPublicKey(domain.curve.createPoint(x, y), domain);

Here d is the raw private key and x and y are the x and y coordinates of the raw public key.


On the JavaScript side, the Base64 encoded raw private key is used and the Base64 encoded raw uncompressed key, where the uncompressed key is the concatenation 0x04 + <x> + <y>. So, for export and import, the following applies:

  • Regarding the export, the Base64 encoded raw private and public key can be derived from d, x, and y as follows:

    String rawPrivateB64 = exportPrivate(d);
    String rawUncompressedPublicB64 = exportPublic(x, y, 32);
    

    with

    import 'dart:convert';
    import 'package:nanodart/nanodart.dart';
    
    String exportPrivate(BigInt d){
      return base64Encode(NanoHelpers.bigIntToBytes(d));
    }
    
    String exportPublic(BigInt x, BigInt y, int size){
      return base64Encode(NanoHelpers.concat([Uint8List.fromList([4]), pad(NanoHelpers.bigIntToBytes(x), size), pad(NanoHelpers.bigIntToBytes(y), size)]));
    }
    
    Uint8List pad(Uint8List list, int size){
      Uint8List padded = list;
      int currSize = list.length;
      if (currSize < size){
        Uint8List pad = Uint8List(size - currSize);
        padded = NanoHelpers.concat([pad, list]);
      }
      return padded;
    }
    

    The size passed in exportPublic() as 3rd parameter is the size of the order of the generator point (32 bytes for secp256r1). If x or y are smaller, they are each padded from the front with 0x00 values until the required length is reached.

    For the conversion from BigInt to Uint8List and the concatenation I used the NanoHelpers class from the NanoDart package for simplicity. Of course, other implementations can be used here as well.

  • Regarding the import, d, x, and y can be derived from the Base64 encoded raw private and public key as follows:

    BigInt d = importPrivate(rawPrivateB64);
    List xy = importPublic(rawUncompressedPublicB64);
    BigInt x = xy.elementAt(0);
    BigInt y = xy.elementAt(1);
    

    with

    BigInt importPrivate(String d){
      return NanoHelpers.byteToBigInt(base64Decode(d));
    }
    
    List importPublic(String xy){
      Uint8List xyBytes = base64Decode(xy);
      xyBytes = xyBytes.sublist(1, xyBytes.length);
      int size = xyBytes.length ~/ 2;
      Uint8List x = xyBytes.sublist(0, size);
      Uint8List y = xyBytes.sublist(size);
      return [NanoHelpers.byteToBigInt(x), NanoHelpers.byteToBigInt(y)];
    }
    

Test

The posted key pair

{"priKey":"CVK3r/UxdGCwQBjtn5vN/orUMxKf9E/1TlJzLkMz9t4=","pubKey":"BJH/mWJqgchGxXGA5/E79SsWRwVo3rpduBmD8FOs7UlKiK8PIvwrkCDvUcwhKdysW35OByPjoVcwFqg1NyumLKM="}

can be imported and used for signing and verification as follows:

String rawPrivateB64 = "CVK3r/UxdGCwQBjtn5vN/orUMxKf9E/1TlJzLkMz9t4=";
String rawUncompressedPublicB64 = "BJH/mWJqgchGxXGA5/E79SsWRwVo3rpduBmD8FOs7UlKiK8PIvwrkCDvUcwhKdysW35OByPjoVcwFqg1NyumLKM=";

BigInt d = importPrivate(rawPrivateB64);
List xy = importPublic(rawUncompressedPublicB64);
BigInt x = xy.elementAt(0);
BigInt y = xy.elementAt(1);

ECDomainParameters domain = ECDomainParameters('secp256r1');
ECPrivateKey private = ECPrivateKey(d, domain);
ECPublicKey public = ECPublicKey(domain.curve.createPoint(x, y), domain);

ECSignature signature = sign(private, "The quick brown fox jumps over the lazy dog");
bool verified = verify("The quick brown fox jumps over the lazy dog", signature, public);
print(verified); // true

Edit: Regarding your comment: sign() and verify() are the methods you posted, but according to the changes, the keys are now passed directly (instead of strings) and the actual message is applied (instead of [1,2,3]) using utf8.encode() for UTF-8 encoding:

import 'dart:convert';

ECSignature sign(ECPrivateKey privateKey, String txt) {
  SecureRandom random = _setRandom();
  Uint8List txtBytes = Uint8List.fromList(utf8.encode(txt));
  PrivateKeyParameter signParams = PrivateKeyParameter(privateKey);
  signer.init(true, ParametersWithRandom(signParams, random));
  ECSignature signature = signer.generateSignature(txtBytes) as ECSignature;
  return signature;
}

bool verify(String txt, ECSignature signature, ECPublicKey publicKey) {
  signer.init(false, PublicKeyParameter(publicKey));
  bool verify = signer.verifySignature(Uint8List.fromList(utf8.encode(txt)), signature);
  return verify;
}

As _setRandom() I applied the implementation from generateKeyPair() instead of your implementation (i.e. a FortunaRandom() based CSPRNG).

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • all right, thanks ,I've used your methods to generate the base64 strings,what's the sign and verify functions look like – John Jun 20 '22 at 04:02
  • @John - See the edit section of my answer please. – Topaco Jun 20 '22 at 06:39
  • all right,I miss that part,3ks – John Jun 20 '22 at 07:13
  • I've post all the codes, and the verify result is true, can you help me check it and give tips if there are – John Jun 20 '22 at 07:28
  • @John - SO is for concrete programming problems. For a code review, https://codereview.stackexchange.com/ is a better address. Thx. – Topaco Jun 20 '22 at 07:32
0

my whole code based on accepted answer

crypto.dart

import "dart:typed_data";
import "dart:math";
import 'dart:convert';

import "package:pointycastle/export.dart";
import './utils.dart';

Signer signer = new Signer('SHA-256/ECDSA');

class Crypto {
  final ECDomainParameters ecDomain = new ECDomainParameters('secp256r1');

  /// 公私钥对生成
  /// 关于公私钥encoding:https://stackoverflow.com/questions/72641616/how-to-convert-asymmetrickeypair-to-base64-encoding-string-in-dart
  ///

  Map generateKeyPair({curve = 'secp256r1'}) {
    var param = curve == 'secp256r1' ? ECCurve_secp256r1() : ECCurve_secp256k1();
    var keyParams = ECKeyGeneratorParameters(param);

    var random = FortunaRandom();
    random.seed(KeyParameter(this._seed(32)));

    var generator = ECKeyGenerator();
    generator.init(ParametersWithRandom(keyParams, random));

    AsymmetricKeyPair<PublicKey, PrivateKey> kp = generator.generateKeyPair();
    BigInt d = (kp.privateKey as ECPrivateKey).d!;
    BigInt x = (kp.publicKey as ECPublicKey).Q!.x!.toBigInteger()!;
    BigInt y = (kp.publicKey as ECPublicKey).Q!.y!.toBigInteger()!;
    String rawPrivateB64 = exportPrivate(d);
    String rawUncompressedPublicB64 = exportPublic(x, y, 32);
    return {'base64Pub': rawUncompressedPublicB64, 'base64Priv': rawPrivateB64};
  }

  Uint8List _seed(size) {
    var random = Random.secure();
    var seed = List<int>.generate(size, (_) => random.nextInt(256));
    return Uint8List.fromList(seed);
  }

// TODO
// Restore the ECPrivateKey from 'd'.
  restoreKeyFromPrivate(privateKeyStr) {
    ECPrivateKey privateKey = new ECPrivateKey(BigInt.parse(privateKeyStr, radix: 10), ecDomain);
    ECPoint Q = privateKey.parameters!.G * privateKey.d as ECPoint;
    ECPublicKey publicKey = new ECPublicKey(Q, privateKey.parameters);
    return publicKey;
  }

  ECSignature sign(ECPrivateKey privateKey, String txt) {
    SecureRandom random = _setRandom();
    Uint8List txtBytes = Uint8List.fromList(utf8.encode(txt));
    PrivateKeyParameter signParams = PrivateKeyParameter(privateKey);
    signer.init(true, ParametersWithRandom(signParams, random));
    ECSignature signature = signer.generateSignature(txtBytes) as ECSignature;
    return signature;
  }

  bool verify(String txt, ECSignature signature, ECPublicKey publicKey) {
    signer.init(false, PublicKeyParameter(publicKey));
    bool verify = signer.verifySignature(Uint8List.fromList(utf8.encode(txt)), signature);
    return verify;
  }

  SecureRandom _setRandom() {
    // List<int> key = [67, 3, 241, 75, 143, 78, 115, 99, 21, 242, 180, 43, 26, 7, 194, 20];
    // List<int> iv = [87, 117, 137, 182, 2, 199, 132, 230, 120, 12, 109, 177, 34, 197, 186, 206];
    List<int> key = _seed(16);
    List<int> iv = _seed(16);
    KeyParameter keyParam = new KeyParameter(new Uint8List.fromList(key));
    ParametersWithIV<KeyParameter> paramsIV = new ParametersWithIV(keyParam, new Uint8List.fromList(iv));
    SecureRandom random = new SecureRandom('AES/CTR/AUTO-SEED-PRNG')..seed(paramsIV);
    return random;
  }
}

crypto_test.dart

import '../lib/crypto/crypto.dart';
import "dart:typed_data";
import "dart:math";
import '../lib/crypto/utils.dart';
import "package:pointycastle/export.dart";

var crypto = new Crypto();
void main() {
  // _generateTest();
  // _signTest();
  Map keyPair = crypto.generateKeyPair();
  String rawPrivateB64 = keyPair['base64Priv'];
  String rawUncompressedPublicB64 = keyPair['base64Pub'];

  BigInt d = importPrivate(rawPrivateB64);
  List xy = importPublic(rawUncompressedPublicB64);
  BigInt x = xy.elementAt(0);
  BigInt y = xy.elementAt(1);

  ECDomainParameters domain = ECDomainParameters('secp256r1');
  ECPrivateKey private = ECPrivateKey(d, domain);
  ECPublicKey public = ECPublicKey(domain.curve.createPoint(x, y), domain);

  ECSignature signature = crypto.sign(private, "The quick brown fox jumps over the lazy dog");
  bool verified = crypto.verify("The quick brown fox jumps over the lazy dog", signature, public);
  print('验签结果');
  print(verified); // true
}

_generateTest() {
  Map keyPair = crypto.generateKeyPair();

  print("结果");
  print(keyPair['base64Pub']);
  print(keyPair['base64Priv']);
}

_signTest() {
  // AsymmetricKeyPair<PublicKey, PrivateKey> kp = crypto.generateKeyPair();
  // ECSignature signature = sign(kp.privateKey as ECPrivateKey, "The quick brown fox jumps over the lazy dog");
  // bool verified = crypto.verify("The quick brown fox jumps over the lazy dog", signature, kp.publicKey as ECPublicKey);
  // print(verified); // true
}
John
  • 645
  • 1
  • 8
  • 23