Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECSignature - base64 string #57

Closed
TMSantos opened this issue Nov 30, 2021 · 2 comments
Closed

ECSignature - base64 string #57

TMSantos opened this issue Nov 30, 2021 · 2 comments

Comments

@TMSantos
Copy link

Hi, I can't find any example, is it possible to explain how to convert ECSignature to base64 signature?

ECSignature ecSign(ECPrivateKey privateKey, Uint8List dataToSign, {String algorithmName = 'SHA-1/ECDSA'});

@FlutterCrypto
Copy link

It takes you through some conversions to get a base64 signature in IEEE P1363 encoding. This encoding means that the two parts of the signature (R and S value, each 32 bytes long) are concatenated to a 64 byte long value.

Kindly keep in mind that this encoding is not compatible to the second encoding type, the DER encoded signature.

Below you find a full running example from my cross platform cryptography project (https://github.com/java-crypto/cross_platform_crypto/tree/main/EcSignatureP256Sha256String), it is using "ECDSA with SHA-256":

import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/asn1/object_identifiers.dart';
import "package:pointycastle/export.dart";
import 'package:basic_utils/basic_utils.dart';

void main() {
/* add in pubspec.yaml:
dependencies:
  pointycastle: ^3.1.1
  basic_utils: ^3.4.0
 */
  // https://pub.dev/packages/pointycastle
  // https://github.com/bcgit/pc-dart/
  // https://pub.dev/packages/basic_utils
  // https://github.com/Ephenodrom/Dart-Basic-Utils

  print('EC signature string (ECDSA with SHA256)');

  final dataToSignString = 'The quick brown fox jumps over the lazy dog';
  final dataToSign = createUint8ListFromString(dataToSignString);
  print('plaintext: ' + dataToSignString);

  // # # # usually we would load the private and public key from a file or keystore # # #
  // # # # here we use hardcoded keys for demonstration - don't do this in real programs # # #

  print('\n* * * sign the plaintext with the EC private key * * *');
  final privateKeyPem = loadEcPrivateKeyPem();
  // get the data from PEM
  Uint8List ecPrivateKeyPkcs8Der =
      CryptoUtils.getBytesFromPEMString(privateKeyPem);
  ECPrivateKey ecPrivateKey =
      ecPrivateKeyFromDerBytesPkcs8(ecPrivateKeyPkcs8Der);
  final signatureBase64 = ecSignToBase64(ecPrivateKey, dataToSign);
  print('signature (Base64): ' + signatureBase64);

  print(
      '\n* * * verify the signature against the plaintext with the EC public key * * *');
  final publicKeyPem = loadEcPublicKeyPem();
  final ecPublicKey =
      CryptoUtils.ecPublicKeyFromPem(publicKeyPem) as ECPublicKey;
  bool signatureVerifiedBase64 =
      ecVerifySignatureFromBase64(ecPublicKey, dataToSign, signatureBase64);
  print('signature (Base64) verified: ' + signatureVerifiedBase64.toString());
}

String ecSignToBase64(ECPrivateKey privateKey, Uint8List messageByte) {
  ECSignature ecSignature = CryptoUtils.ecSign(privateKey, messageByte,
      algorithmName: 'SHA-256/ECDSA');
  BigInt r = ecSignature.r;
  BigInt s = ecSignature.s;
  Uint8List rUint8ListV2 = encodeBigInt(r) as Uint8List;
  Uint8List sUint8ListV2 = encodeBigInt(s) as Uint8List;
  var bbV2 = BytesBuilder();
  bbV2.add(rUint8ListV2);
  bbV2.add(sUint8ListV2);
  var signature = bbV2.toBytes();
  return base64Encoding(signature);
}

bool ecVerifySignatureFromBase64(
    ECPublicKey publicKey, Uint8List messageByte, String signatureBase64) {
  var signatureValue = base64Decoding(signatureBase64);
  BigInt r = decodeBigInt(Uint8List.sublistView(signatureValue, 0, 32));
  BigInt s = decodeBigInt(Uint8List.sublistView(signatureValue, 32, 64));
  ECSignature signature = new ECSignature(r, s);
  return CryptoUtils.ecVerify(publicKey, messageByte, signature,
      algorithm: 'SHA-256/ECDSA');
}

Uint8List createUint8ListFromString(String s) {
  var ret = new Uint8List(s.length);
  for (var i = 0; i < s.length; i++) {
    ret[i] = s.codeUnitAt(i);
  }
  return ret;
}

// http://phoenix.yizimg.com/ethereumdart/rlp/blob/master/lib/src/pointycastle-utils.dart
/// Decode a BigInt from bytes in big-endian encoding.
BigInt decodeBigInt(List<int> bytes) {
  BigInt result = new BigInt.from(0);
  for (int i = 0; i < bytes.length; i++) {
    result += new BigInt.from(bytes[bytes.length - i - 1]) << (8 * i);
  }
  return result;
}

var _byteMask = new BigInt.from(0xff);

/// Encode a BigInt into bytes using big-endian encoding.
Uint8List encodeBigInt(BigInt number) {
  // Not handling negative numbers. Decide how you want to do that.
  int size = (number.bitLength + 7) >> 3;
  var result = new Uint8List(size);
  for (int i = 0; i < size; i++) {
    result[size - i - 1] = (number & _byteMask).toInt();
    number = number >> 8;
  }
  return result;
}

///
/// Decode the given [bytes] in PKCS8 encoding into an [ECPrivateKey].
///
ECPrivateKey ecPrivateKeyFromDerBytesPkcs8(Uint8List bytes) {
  ASN1Parser asn1Parser = ASN1Parser(bytes);
  ASN1Sequence topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
  ASN1Sequence innerSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence;
  ASN1ObjectIdentifier b2 =
      innerSeq.elements!.elementAt(1) as ASN1ObjectIdentifier;
  String? b2Data = b2.objectIdentifierAsString;
  Map<String, dynamic>? b2Curvedata =
      ObjectIdentifiers.getIdentifierByIdentifier(b2Data);
  dynamic curveName;
  if (b2Curvedata != null) {
    curveName = b2Curvedata['readableName'];
  }
  // get the octet string data for the private key der data
  ASN1OctetString octetString =
      topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
  asn1Parser = ASN1Parser(octetString.valueBytes);
  ASN1Sequence octetStringSeq = asn1Parser.nextObject() as ASN1Sequence;
  ASN1OctetString octetStringKeyData =
      octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
  // now generate the key
  Uint8List privateKeyDer = octetStringKeyData.valueBytes!;
  return ECPrivateKey(
      decodeBigInt(privateKeyDer), ECDomainParameters(curveName));
}

// don't worry - it's a sample key
String loadEcPublicKeyPem() {
  return ('''-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMZHnt1D1tddLNGlEHXEtEw5K/G5Q
HkgaLi+IV84oiV+THv/DqGxYDX2F5JOkfyv36iYSf5lfIC7q9el4YLnlwA==
-----END PUBLIC KEY-----''');
}

// don't worry - it's a sample key
String loadEcPrivateKeyPem() {
  return ('''-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgciPt/gulzw7/Xe12
YOu/vLUgIUZ+7gGo5VkmU0B+gUWhRANCAAQxkee3UPW110s0aUQdcS0TDkr8blAe
SBouL4hXziiJX5Me/8OobFgNfYXkk6R/K/fqJhJ/mV8gLur16XhgueXA
-----END PRIVATE KEY-----''');
}

String base64Encoding(Uint8List input) {
  return base64.encode(input);
}

Uint8List base64Decoding(String input) {
  return base64.decode(input);
}

@Ephenodrom
Copy link
Owner

This can be considered as closed as with the latest commits it is now possible to create a base64 signature following the ASN1 structure from https://datatracker.ietf.org/doc/html/rfc5480#page-17.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants