|
|
- import 'dart:convert';
- import 'dart:math';
- import 'dart:typed_data';
-
- import 'package:pointycastle/asn1/object_identifiers.dart';
- import 'package:pointycastle/export.dart';
- import 'package:pointycastle/pointycastle.dart';
- import 'package:pointycastle/src/utils.dart' as thing;
- import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp;
-
- import './string_utils.dart';
-
- ///
- /// Helper class for cryptographic operations
- ///
- class CryptoUtils {
- static const BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----';
- static const END_PRIVATE_KEY = '-----END PRIVATE KEY-----';
-
- static const BEGIN_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----';
- static const END_PUBLIC_KEY = '-----END PUBLIC KEY-----';
-
- static const BEGIN_EC_PRIVATE_KEY = '-----BEGIN EC PRIVATE KEY-----';
- static const END_EC_PRIVATE_KEY = '-----END EC PRIVATE KEY-----';
-
- static const BEGIN_EC_PUBLIC_KEY = '-----BEGIN EC PUBLIC KEY-----';
- static const END_EC_PUBLIC_KEY = '-----END EC PUBLIC KEY-----';
-
- static const BEGIN_RSA_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----';
- static const END_RSA_PRIVATE_KEY = '-----END RSA PRIVATE KEY-----';
-
- static const BEGIN_RSA_PUBLIC_KEY = '-----BEGIN RSA PUBLIC KEY-----';
- static const END_RSA_PUBLIC_KEY = '-----END RSA PUBLIC KEY-----';
-
- ///
- /// Converts the [RSAPublicKey.modulus] from the given [publicKey] to a [Uint8List].
- ///
- static Uint8List rsaPublicKeyModulusToBytes(RSAPublicKey publicKey) =>
- thing.encodeBigInt(publicKey.modulus);
-
- ///
- /// Converts the [RSAPublicKey.exponent] from the given [publicKey] to a [Uint8List].
- ///
- static Uint8List rsaPublicKeyExponentToBytes(RSAPublicKey publicKey) =>
- thing.encodeBigInt(publicKey.exponent);
-
- ///
- /// Converts the [RSAPrivateKey.modulus] from the given [privateKey] to a [Uint8List].
- ///
- static Uint8List rsaPrivateKeyModulusToBytes(RSAPrivateKey privateKey) =>
- thing.encodeBigInt(privateKey.modulus);
-
- ///
- /// Converts the [RSAPrivateKey.exponent] from the given [privateKey] to a [Uint8List].
- ///
- static Uint8List rsaPrivateKeyExponentToBytes(RSAPrivateKey privateKey) =>
- thing.encodeBigInt(privateKey.exponent);
-
- ///
- /// Get a SHA1 Thumbprint for the given [bytes].
- ///
- @Deprecated('Use [getHash]')
- static String getSha1ThumbprintFromBytes(Uint8List bytes) {
- return getHash(bytes, algorithmName: 'SHA-1');
- }
-
- ///
- /// Get a SHA256 Thumbprint for the given [bytes].
- ///
- @Deprecated('Use [getHash]')
- static String getSha256ThumbprintFromBytes(Uint8List bytes) {
- return getHash(bytes, algorithmName: 'SHA-256');
- }
-
- ///
- /// Get a MD5 Thumbprint for the given [bytes].
- ///
- @Deprecated('Use [getHash]')
- static String getMd5ThumbprintFromBytes(Uint8List bytes) {
- return getHash(bytes, algorithmName: 'MD5');
- }
-
- ///
- /// Get a hash for the given [bytes] using the given [algorithm]
- ///
- /// The default [algorithm] used is **SHA-256**. All supported algorihms are :
- ///
- /// * SHA-1
- /// * SHA-224
- /// * SHA-256
- /// * SHA-384
- /// * SHA-512
- /// * SHA-512/224
- /// * SHA-512/256
- /// * MD5
- ///
- static String getHash(Uint8List bytes, {String algorithmName = 'SHA-256'}) {
- var hash = getHashPlain(bytes, algorithmName: algorithmName);
-
- const hexDigits = '0123456789abcdef';
- var charCodes = Uint8List(hash.length * 2);
- for (var i = 0, j = 0; i < hash.length; i++) {
- var byte = hash[i];
- charCodes[j++] = hexDigits.codeUnitAt((byte >> 4) & 0xF);
- charCodes[j++] = hexDigits.codeUnitAt(byte & 0xF);
- }
-
- return String.fromCharCodes(charCodes).toUpperCase();
- }
-
- ///
- /// Get a hash for the given [bytes] using the given [algorithm]
- ///
- /// The default [algorithm] used is **SHA-256**. All supported algorihms are :
- ///
- /// * SHA-1
- /// * SHA-224
- /// * SHA-256
- /// * SHA-384
- /// * SHA-512
- /// * SHA-512/224
- /// * SHA-512/256
- /// * MD5
- ///
- static Uint8List getHashPlain(Uint8List bytes,
- {String algorithmName = 'SHA-256'}) {
- Uint8List hash;
- switch (algorithmName) {
- case 'SHA-1':
- hash = Digest('SHA-1').process(bytes);
- break;
- case 'SHA-224':
- hash = Digest('SHA-224').process(bytes);
- break;
- case 'SHA-256':
- hash = Digest('SHA-256').process(bytes);
- break;
- case 'SHA-384':
- hash = Digest('SHA-384').process(bytes);
- break;
- case 'SHA-512':
- hash = Digest('SHA-512').process(bytes);
- break;
- case 'SHA-512/224':
- hash = Digest('SHA-512/224').process(bytes);
- break;
- case 'SHA-512/256':
- hash = Digest('SHA-512/256').process(bytes);
- break;
- case 'MD5':
- hash = Digest('MD5').process(bytes);
- break;
- default:
- throw ArgumentError('Hash not supported');
- }
-
- return hash;
- }
-
- ///
- /// Generates a RSA [AsymmetricKeyPair] with the given [keySize].
- /// The default value for the [keySize] is 2048 bits.
- ///
- /// The following keySize is supported:
- /// * 1024
- /// * 2048
- /// * 4096
- /// * 8192
- ///
- static AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateRSAKeyPair({int keySize = 2048}) {
- var keyParams =
- RSAKeyGeneratorParameters(BigInt.parse('65537'), keySize, 12);
-
- var secureRandom = _getSecureRandom();
-
- var rngParams = ParametersWithRandom(keyParams, secureRandom);
- var generator = RSAKeyGenerator();
- generator.init(rngParams);
-
- final pair = generator.generateKeyPair();
-
- final myPublic = pair.publicKey as RSAPublicKey;
- final myPrivate = pair.privateKey as RSAPrivateKey;
-
- return AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey>(myPublic, myPrivate);
- }
-
- ///
- /// Generates a elliptic curve [AsymmetricKeyPair].
- ///
- /// The default curve is **prime256v1**
- ///
- /// The following curves are supported:
- ///
- /// * brainpoolp160r1
- /// * brainpoolp160t1
- /// * brainpoolp192r1
- /// * brainpoolp192t1
- /// * brainpoolp224r1
- /// * brainpoolp224t1
- /// * brainpoolp256r1
- /// * brainpoolp256t1
- /// * brainpoolp320r1
- /// * brainpoolp320t1
- /// * brainpoolp384r1
- /// * brainpoolp384t1
- /// * brainpoolp512r1
- /// * brainpoolp512t1
- /// * GostR3410-2001-CryptoPro-A
- /// * GostR3410-2001-CryptoPro-B
- /// * GostR3410-2001-CryptoPro-C
- /// * GostR3410-2001-CryptoPro-XchA
- /// * GostR3410-2001-CryptoPro-XchB
- /// * prime192v1
- /// * prime192v2
- /// * prime192v3
- /// * prime239v1
- /// * prime239v2
- /// * prime239v3
- /// * prime256v1
- /// * secp112r1
- /// * secp112r2
- /// * secp128r1
- /// * secp128r2
- /// * secp160k1
- /// * secp160r1
- /// * secp160r2
- /// * secp192k1
- /// * secp192r1
- /// * secp224k1
- /// * secp224r1
- /// * secp256k1
- /// * secp256r1
- /// * secp384r1
- /// * secp521r1
- ///
- static AsymmetricKeyPair generateEcKeyPair({String curve = 'prime256v1'}) {
- var ecDomainParameters = ECDomainParameters(curve);
- var keyParams = ECKeyGeneratorParameters(ecDomainParameters);
-
- var secureRandom = _getSecureRandom();
-
- var rngParams = ParametersWithRandom(keyParams, secureRandom);
- var generator = ECKeyGenerator();
- generator.init(rngParams);
-
- return generator.generateKeyPair();
- }
-
- ///
- /// Generates a secure [FortunaRandom]
- ///
- static SecureRandom _getSecureRandom() {
- var secureRandom = FortunaRandom();
- var random = Random.secure();
- var seeds = <int>[];
- for (var i = 0; i < 32; i++) {
- seeds.add(random.nextInt(255));
- }
- secureRandom.seed(KeyParameter(Uint8List.fromList(seeds)));
- return secureRandom;
- }
-
- ///
- /// Enode the given [publicKey] to PEM format using the PKCS#8 standard.
- ///
- static String encodeRSAPublicKeyToPem(RSAPublicKey publicKey) {
- var algorithmSeq = ASN1Sequence();
- var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
- algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
- algorithmSeq.add(paramsAsn1Obj);
-
- var publicKeySeq = ASN1Sequence();
- publicKeySeq.add(ASN1Integer(publicKey.modulus));
- publicKeySeq.add(ASN1Integer(publicKey.exponent));
- var publicKeySeqBitString =
- ASN1BitString(stringValues: Uint8List.fromList(publicKeySeq.encode()));
-
- var topLevelSeq = ASN1Sequence();
- topLevelSeq.add(algorithmSeq);
- topLevelSeq.add(publicKeySeqBitString);
- var dataBase64 = base64.encode(topLevelSeq.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
-
- return '$BEGIN_PUBLIC_KEY\n${chunks.join('\n')}\n$END_PUBLIC_KEY';
- }
-
- ///
- /// Enode the given [rsaPublicKey] to PEM format using the PKCS#1 standard.
- ///
- /// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc8017#page-53>.
- ///
- /// ```
- /// RSAPublicKey ::= SEQUENCE {
- /// modulus INTEGER, -- n
- /// publicExponent INTEGER -- e
- /// }
- /// ```
- ///
- static String encodeRSAPublicKeyToPemPkcs1(RSAPublicKey rsaPublicKey) {
- var topLevelSeq = ASN1Sequence();
- topLevelSeq.add(ASN1Integer(rsaPublicKey.modulus));
- topLevelSeq.add(ASN1Integer(rsaPublicKey.exponent));
-
- var dataBase64 = base64.encode(topLevelSeq.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
-
- return '$BEGIN_RSA_PUBLIC_KEY\n${chunks.join('\n')}\n$END_RSA_PUBLIC_KEY';
- }
-
- ///
- /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#1 standard.
- ///
- /// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc8017#page-54>.
- ///
- /// ```
- /// RSAPrivateKey ::= SEQUENCE {
- /// version Version,
- /// modulus INTEGER, -- n
- /// publicExponent INTEGER, -- e
- /// privateExponent INTEGER, -- d
- /// prime1 INTEGER, -- p
- /// prime2 INTEGER, -- q
- /// exponent1 INTEGER, -- d mod (p-1)
- /// exponent2 INTEGER, -- d mod (q-1)
- /// coefficient INTEGER, -- (inverse of q) mod p
- /// otherPrimeInfos OtherPrimeInfos OPTIONAL
- /// }
- /// ```
- static String encodeRSAPrivateKeyToPemPkcs1(RSAPrivateKey rsaPrivateKey) {
- var version = ASN1Integer(BigInt.from(0));
- var modulus = ASN1Integer(rsaPrivateKey.n);
- var publicExponent = ASN1Integer(BigInt.parse('65537'));
- var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent);
-
- var p = ASN1Integer(rsaPrivateKey.p);
- var q = ASN1Integer(rsaPrivateKey.q);
- var dP =
- rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1));
- var exp1 = ASN1Integer(dP);
- var dQ =
- rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1));
- var exp2 = ASN1Integer(dQ);
- var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!);
- var co = ASN1Integer(iQ);
-
- var topLevelSeq = ASN1Sequence();
- topLevelSeq.add(version);
- topLevelSeq.add(modulus);
- topLevelSeq.add(publicExponent);
- topLevelSeq.add(privateExponent);
- topLevelSeq.add(p);
- topLevelSeq.add(q);
- topLevelSeq.add(exp1);
- topLevelSeq.add(exp2);
- topLevelSeq.add(co);
- var dataBase64 = base64.encode(topLevelSeq.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
- return '$BEGIN_RSA_PRIVATE_KEY\n${chunks.join('\n')}\n$END_RSA_PRIVATE_KEY';
- }
-
- ///
- /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#8 standard.
- ///
- /// The ASN1 structure is decripted at <https://tools.ietf.org/html/rfc5208>.
- /// ```
- /// PrivateKeyInfo ::= SEQUENCE {
- /// version Version,
- /// algorithm AlgorithmIdentifier,
- /// PrivateKey BIT STRING
- /// }
- /// ```
- ///
- static String encodeRSAPrivateKeyToPem(RSAPrivateKey rsaPrivateKey) {
- var version = ASN1Integer(BigInt.from(0));
-
- var algorithmSeq = ASN1Sequence();
- var algorithmAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList(
- [0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
- var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
- algorithmSeq.add(algorithmAsn1Obj);
- algorithmSeq.add(paramsAsn1Obj);
-
- var privateKeySeq = ASN1Sequence();
- var modulus = ASN1Integer(rsaPrivateKey.n);
- var publicExponent = ASN1Integer(BigInt.parse('65537'));
- var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent);
- var p = ASN1Integer(rsaPrivateKey.p);
- var q = ASN1Integer(rsaPrivateKey.q);
- var dP =
- rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1));
- var exp1 = ASN1Integer(dP);
- var dQ =
- rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1));
- var exp2 = ASN1Integer(dQ);
- var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!);
- var co = ASN1Integer(iQ);
-
- privateKeySeq.add(version);
- privateKeySeq.add(modulus);
- privateKeySeq.add(publicExponent);
- privateKeySeq.add(privateExponent);
- privateKeySeq.add(p);
- privateKeySeq.add(q);
- privateKeySeq.add(exp1);
- privateKeySeq.add(exp2);
- privateKeySeq.add(co);
- var publicKeySeqOctetString =
- ASN1OctetString(octets: Uint8List.fromList(privateKeySeq.encode()));
-
- var topLevelSeq = ASN1Sequence();
- topLevelSeq.add(version);
- topLevelSeq.add(algorithmSeq);
- topLevelSeq.add(publicKeySeqOctetString);
- var dataBase64 = base64.encode(topLevelSeq.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
- return '$BEGIN_PRIVATE_KEY\n${chunks.join('\n')}\n$END_PRIVATE_KEY';
- }
-
- ///
- /// Decode a [RSAPrivateKey] from the given [pem] String.
- ///
- static RSAPrivateKey rsaPrivateKeyFromPem(String pem) {
- var bytes = getBytesFromPEMString(pem);
- return rsaPrivateKeyFromDERBytes(bytes);
- }
-
- ///
- /// Decode the given [bytes] into an [RSAPrivateKey].
- ///
- static RSAPrivateKey rsaPrivateKeyFromDERBytes(Uint8List bytes) {
- var asn1Parser = ASN1Parser(bytes);
- var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
- //ASN1Object version = topLevelSeq.elements[0];
- //ASN1Object algorithm = topLevelSeq.elements[1];
- var privateKey = topLevelSeq.elements![2];
-
- asn1Parser = ASN1Parser(privateKey.valueBytes);
- var pkSeq = asn1Parser.nextObject() as ASN1Sequence;
-
- var modulus = pkSeq.elements![1] as ASN1Integer;
- //ASN1Integer publicExponent = pkSeq.elements[2] as ASN1Integer;
- var privateExponent = pkSeq.elements![3] as ASN1Integer;
- var p = pkSeq.elements![4] as ASN1Integer;
- var q = pkSeq.elements![5] as ASN1Integer;
- //ASN1Integer exp1 = pkSeq.elements[6] as ASN1Integer;
- //ASN1Integer exp2 = pkSeq.elements[7] as ASN1Integer;
- //ASN1Integer co = pkSeq.elements[8] as ASN1Integer;
-
- var rsaPrivateKey = RSAPrivateKey(
- modulus.integer!, privateExponent.integer!, p.integer, q.integer);
-
- return rsaPrivateKey;
- }
-
- ///
- /// Decode a [RSAPrivateKey] from the given [pem] string formated in the pkcs1 standard.
- ///
- static RSAPrivateKey rsaPrivateKeyFromPemPkcs1(String pem) {
- var bytes = getBytesFromPEMString(pem);
- return rsaPrivateKeyFromDERBytesPkcs1(bytes);
- }
-
- ///
- /// Decode the given [bytes] into an [RSAPrivateKey].
- ///
- /// The [bytes] need to follow the the pkcs1 standard
- ///
- static RSAPrivateKey rsaPrivateKeyFromDERBytesPkcs1(Uint8List bytes) {
- var asn1Parser = ASN1Parser(bytes);
- var pkSeq = asn1Parser.nextObject() as ASN1Sequence;
-
- var modulus = pkSeq.elements![1] as ASN1Integer;
- //ASN1Integer publicExponent = pkSeq.elements[2] as ASN1Integer;
- var privateExponent = pkSeq.elements![3] as ASN1Integer;
- var p = pkSeq.elements![4] as ASN1Integer;
- var q = pkSeq.elements![5] as ASN1Integer;
- //ASN1Integer exp1 = pkSeq.elements[6] as ASN1Integer;
- //ASN1Integer exp2 = pkSeq.elements[7] as ASN1Integer;
- //ASN1Integer co = pkSeq.elements[8] as ASN1Integer;
-
- var rsaPrivateKey = RSAPrivateKey(
- modulus.integer!, privateExponent.integer!, p.integer, q.integer);
-
- return rsaPrivateKey;
- }
-
- ///
- /// Helper function for decoding the base64 in [pem].
- ///
- /// Throws an ArgumentError if the given [pem] is not sourounded by begin marker -----BEGIN and
- /// endmarker -----END or the [pem] consists of less than two lines.
- ///
- /// The PEM header check can be skipped by setting the optional paramter [checkHeader] to false.
- ///
- static Uint8List getBytesFromPEMString(String pem,
- {bool checkHeader = true}) {
- var lines = LineSplitter.split(pem)
- .map((line) => line.trim())
- .where((line) => line.isNotEmpty)
- .toList();
- var base64;
- if (checkHeader) {
- if (lines.length < 2 ||
- !lines.first.startsWith('-----BEGIN') ||
- !lines.last.startsWith('-----END')) {
- throw ArgumentError('The given string does not have the correct '
- 'begin/end markers expected in a PEM file.');
- }
- base64 = lines.sublist(1, lines.length - 1).join('');
- } else {
- base64 = lines.join('');
- }
-
- return Uint8List.fromList(base64Decode(base64));
- }
-
- ///
- /// Decode a [RSAPublicKey] from the given [pem] String.
- ///
- static RSAPublicKey rsaPublicKeyFromPem(String pem) {
- var bytes = CryptoUtils.getBytesFromPEMString(pem);
- return rsaPublicKeyFromDERBytes(bytes);
- }
-
- ///
- /// Decode the given [bytes] into an [RSAPublicKey].
- ///
- static RSAPublicKey rsaPublicKeyFromDERBytes(Uint8List bytes) {
- var asn1Parser = ASN1Parser(bytes);
- var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
- var publicKeySeq;
- if (topLevelSeq.elements![1].runtimeType == ASN1BitString) {
- var publicKeyBitString = topLevelSeq.elements![1] as ASN1BitString;
-
- var publicKeyAsn =
- ASN1Parser(publicKeyBitString.stringValues as Uint8List?);
- publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence;
- } else {
- publicKeySeq = topLevelSeq;
- }
- var modulus = publicKeySeq.elements![0] as ASN1Integer;
- var exponent = publicKeySeq.elements![1] as ASN1Integer;
-
- var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!);
-
- return rsaPublicKey;
- }
-
- ///
- /// Decode a [RSAPublicKey] from the given [pem] string formated in the pkcs1 standard.
- ///
- static RSAPublicKey rsaPublicKeyFromPemPkcs1(String pem) {
- var bytes = CryptoUtils.getBytesFromPEMString(pem);
- return rsaPublicKeyFromDERBytesPkcs1(bytes);
- }
-
- ///
- /// Decode the given [bytes] into an [RSAPublicKey].
- ///
- /// The [bytes] need to follow the the pkcs1 standard
- ///
- static RSAPublicKey rsaPublicKeyFromDERBytesPkcs1(Uint8List bytes) {
- var publicKeyAsn = ASN1Parser(bytes);
- var publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence;
- var modulus = publicKeySeq.elements![0] as ASN1Integer;
- var exponent = publicKeySeq.elements![1] as ASN1Integer;
-
- var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!);
- return rsaPublicKey;
- }
-
- ///
- /// Enode the given elliptic curve [publicKey] to PEM format.
- ///
- /// This is descripted in <https://tools.ietf.org/html/rfc5915>
- ///
- /// ```ASN1
- /// ECPrivateKey ::= SEQUENCE {
- /// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
- /// privateKey OCTET STRING
- /// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL
- /// publicKey [1] BIT STRING OPTIONAL
- /// }
- ///
- /// ```
- ///
- /// As descripted in the mentioned RFC, all optional values will always be set.
- ///
- static String encodeEcPrivateKeyToPem(ECPrivateKey ecPrivateKey) {
- var outer = ASN1Sequence();
-
- var version = ASN1Integer(BigInt.from(1));
- var privateKeyAsBytes = thing.encodeBigInt(ecPrivateKey.d);
- var privateKey = ASN1OctetString(octets: privateKeyAsBytes);
- var choice = ASN1Sequence(tag: 0xA0);
-
- choice.add(
- ASN1ObjectIdentifier.fromName(ecPrivateKey.parameters!.domainName));
-
- var publicKey = ASN1Sequence(tag: 0xA1);
-
- var subjectPublicKey = ASN1BitString(
- stringValues: ecPrivateKey.parameters!.G.getEncoded(false));
- publicKey.add(subjectPublicKey);
-
- outer.add(version);
- outer.add(privateKey);
- outer.add(choice);
- outer.add(publicKey);
- var dataBase64 = base64.encode(outer.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
-
- return '$BEGIN_EC_PRIVATE_KEY\n${chunks.join('\n')}\n$END_EC_PRIVATE_KEY';
- }
-
- ///
- /// Enode the given elliptic curve [publicKey] to PEM format.
- ///
- /// This is descripted in <https://tools.ietf.org/html/rfc5480>
- ///
- /// ```ASN1
- /// SubjectPublicKeyInfo ::= SEQUENCE {
- /// algorithm AlgorithmIdentifier,
- /// subjectPublicKey BIT STRING
- /// }
- /// ```
- ///
- static String encodeEcPublicKeyToPem(ECPublicKey publicKey) {
- var outer = ASN1Sequence();
- var algorithm = ASN1Sequence();
- algorithm.add(ASN1ObjectIdentifier.fromName('ecPublicKey'));
- algorithm.add(ASN1ObjectIdentifier.fromName('prime256v1'));
- var encodedBytes = publicKey.Q!.getEncoded(false);
-
- var subjectPublicKey = ASN1BitString(stringValues: encodedBytes);
-
- outer.add(algorithm);
- outer.add(subjectPublicKey);
- var dataBase64 = base64.encode(outer.encode());
- var chunks = StringUtils.chunk(dataBase64, 64);
-
- return '$BEGIN_EC_PUBLIC_KEY\n${chunks.join('\n')}\n$END_EC_PUBLIC_KEY';
- }
-
- ///
- /// Decode a [ECPublicKey] from the given [pem] String.
- ///
- /// Throws an ArgumentError if the given string [pem] is null or empty.
- ///
- static ECPublicKey ecPublicKeyFromPem(String pem) {
- if (pem.isEmpty) {
- throw ArgumentError('Argument must not be null.');
- }
- var bytes = CryptoUtils.getBytesFromPEMString(pem);
- return ecPublicKeyFromDerBytes(bytes);
- }
-
- ///
- /// Decode a [ECPrivateKey] from the given [pem] String.
- ///
- /// Throws an ArgumentError if the given string [pem] is null or empty.
- ///
- static ECPrivateKey ecPrivateKeyFromPem(String pem) {
- if (pem.isEmpty) {
- throw ArgumentError('Argument must not be null.');
- }
- var bytes = CryptoUtils.getBytesFromPEMString(pem);
- return ecPrivateKeyFromDerBytes(
- bytes,
- pkcs8: pem.startsWith(BEGIN_PRIVATE_KEY),
- );
- }
-
- ///
- /// Decode the given [bytes] into an [ECPrivateKey].
- ///
- /// [pkcs8] defines the ASN1 format of the given [bytes]. The default is false, so SEC1 is assumed.
- ///
- /// Supports SEC1 (<https://tools.ietf.org/html/rfc5915>) and PKCS8 (<https://datatracker.ietf.org/doc/html/rfc5208>)
- ///
- static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes,
- {bool pkcs8 = false}) {
- var asn1Parser = ASN1Parser(bytes);
- var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
- var curveName;
- var x;
- if (pkcs8) {
- // Parse the PKCS8 format
- var innerSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence;
- var b2 = innerSeq.elements!.elementAt(1) as ASN1ObjectIdentifier;
- var b2Data = b2.objectIdentifierAsString;
- var b2Curvedata = ObjectIdentifiers.getIdentifierByIdentifier(b2Data);
- if (b2Curvedata != null) {
- curveName = b2Curvedata['readableName'];
- }
-
- var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
- asn1Parser = ASN1Parser(octetString.valueBytes);
- var octetStringSeq = asn1Parser.nextObject() as ASN1Sequence;
- var octetStringKeyData =
- octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
-
- x = octetStringKeyData.valueBytes!;
- } else {
- // Parse the SEC1 format
- var privateKeyAsOctetString =
- topLevelSeq.elements!.elementAt(1) as ASN1OctetString;
- var choice = topLevelSeq.elements!.elementAt(2);
- var s = ASN1Sequence();
- var parser = ASN1Parser(choice.valueBytes);
- while (parser.hasNext()) {
- s.add(parser.nextObject());
- }
- var curveNameOi = s.elements!.elementAt(0) as ASN1ObjectIdentifier;
- var data = ObjectIdentifiers.getIdentifierByIdentifier(
- curveNameOi.objectIdentifierAsString);
- if (data != null) {
- curveName = data['readableName'];
- }
-
- x = privateKeyAsOctetString.valueBytes!;
- }
-
- return ECPrivateKey(thing.decodeBigInt(x), ECDomainParameters(curveName));
- }
-
- ///
- /// Decode the given [bytes] into an [ECPublicKey].
- ///
- static ECPublicKey ecPublicKeyFromDerBytes(Uint8List bytes) {
- if (bytes.elementAt(0) == 0) {
- bytes = bytes.sublist(1);
- }
- var asn1Parser = ASN1Parser(bytes);
- var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
-
- var algorithmIdentifierSequence = topLevelSeq.elements![0] as ASN1Sequence;
- var curveNameOi = algorithmIdentifierSequence.elements!.elementAt(1)
- as ASN1ObjectIdentifier;
- var curveName;
- var data = ObjectIdentifiers.getIdentifierByIdentifier(
- curveNameOi.objectIdentifierAsString);
- if (data != null) {
- curveName = data['readableName'];
- }
-
- var subjectPublicKey = topLevelSeq.elements![1] as ASN1BitString;
- var compressed = false;
- var pubBytes = subjectPublicKey.valueBytes!;
- if (pubBytes.elementAt(0) == 0) {
- pubBytes = pubBytes.sublist(1);
- }
-
- // Looks good so far!
- var firstByte = pubBytes.elementAt(0);
- if (firstByte != 4) {
- compressed = true;
- }
- var x = pubBytes.sublist(1, (pubBytes.length / 2).round());
- var y = pubBytes.sublist(1 + x.length, pubBytes.length);
- var params = ECDomainParameters(curveName);
- var bigX = thing.decodeBigIntWithSign(1, x);
- var bigY = thing.decodeBigIntWithSign(1, y);
- var pubKey = ECPublicKey(
- ecc_fp.ECPoint(
- params.curve as ecc_fp.ECCurve,
- params.curve.fromBigInteger(bigX) as ecc_fp.ECFieldElement?,
- params.curve.fromBigInteger(bigY) as ecc_fp.ECFieldElement?,
- compressed),
- params);
- return pubKey;
- }
-
- ///
- /// Encrypt the given [message] using the given RSA [publicKey].
- ///
- static Uint8List rsaEncrypt(Uint8List message, RSAPublicKey publicKey) {
- var cipher = OAEPEncoding.withSHA256(RSAEngine())
- ..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));
-
- return _processInBlocks(cipher, message);
- }
-
- ///
- /// Decrypt the given [cipherMessage] using the given RSA [privateKey].
- ///
- static Uint8List rsaDecrypt(Uint8List cipherMessage, RSAPrivateKey privateKey) {
- var cipher = OAEPEncoding.withSHA256(RSAEngine())
- ..init(false, PrivateKeyParameter<RSAPrivateKey>(privateKey));
-
- return _processInBlocks(cipher, cipherMessage);
- }
-
- static Uint8List _processInBlocks(AsymmetricBlockCipher engine, Uint8List input) {
- final numBlocks = input.length ~/ engine.inputBlockSize +
- ((input.length % engine.inputBlockSize != 0) ? 1 : 0);
-
- final output = Uint8List(numBlocks * engine.outputBlockSize);
-
- var inputOffset = 0;
- var outputOffset = 0;
- while (inputOffset < input.length) {
- final chunkSize = (inputOffset + engine.inputBlockSize <= input.length)
- ? engine.inputBlockSize
- : input.length - inputOffset;
-
- outputOffset += engine.processBlock(
- input, inputOffset, chunkSize, output, outputOffset);
-
- inputOffset += chunkSize;
- }
-
- return (output.length == outputOffset)
- ? output
- : output.sublist(0, outputOffset);
- }
-
- ///
- /// Signing the given [dataToSign] with the given [privateKey].
- ///
- /// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are :
- ///
- /// * MD2/RSA
- /// * MD4/RSA
- /// * MD5/RSA
- /// * RIPEMD-128/RSA
- /// * RIPEMD-160/RSA
- /// * RIPEMD-256/RSA
- /// * SHA-1/RSA
- /// * SHA-224/RSA
- /// * SHA-256/RSA
- /// * SHA-384/RSA
- /// * SHA-512/RSA
- ///
- static Uint8List rsaSign(RSAPrivateKey privateKey, Uint8List dataToSign,
- {String algorithmName = 'SHA-256/RSA'}) {
- var signer = Signer(algorithmName) as RSASigner;
-
- signer.init(true, PrivateKeyParameter<RSAPrivateKey>(privateKey));
-
- var sig = signer.generateSignature(dataToSign);
-
- return sig.bytes;
- }
-
- ///
- /// Verifying the given [signedData] with the given [publicKey] and the given [signature].
- /// Will return **true** if the given [signature] matches the [signedData].
- ///
- /// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are :
- ///
- /// * MD2/RSA
- /// * MD4/RSA
- /// * MD5/RSA
- /// * RIPEMD-128/RSA
- /// * RIPEMD-160/RSA
- /// * RIPEMD-256/RSA
- /// * SHA-1/RSA
- /// * SHA-224/RSA
- /// * SHA-256/RSA
- /// * SHA-384/RSA
- /// * SHA-512/RSA
- ///
- static bool rsaVerify(
- RSAPublicKey publicKey, Uint8List signedData, Uint8List signature,
- {String algorithm = 'SHA-256/RSA'}) {
- final sig = RSASignature(signature);
-
- final verifier = Signer(algorithm);
-
- verifier.init(false, PublicKeyParameter<RSAPublicKey>(publicKey));
-
- try {
- return verifier.verifySignature(signedData, sig);
- } on ArgumentError {
- return false;
- }
- }
-
- ///
- /// Signing the given [dataToSign] with the given [privateKey].
- ///
- /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are :
- ///
- /// * SHA-1/ECDSA
- /// * SHA-224/ECDSA
- /// * SHA-256/ECDSA
- /// * SHA-384/ECDSA
- /// * SHA-512/ECDSA
- /// * SHA-1/DET-ECDSA
- /// * SHA-224/DET-ECDSA
- /// * SHA-256/DET-ECDSA
- /// * SHA-384/DET-ECDSA
- /// * SHA-512/DET-ECDSA
- ///
- static ECSignature ecSign(ECPrivateKey privateKey, Uint8List dataToSign,
- {String algorithmName = 'SHA-1/ECDSA'}) {
- var signer = Signer(algorithmName) as ECDSASigner;
-
- var params = ParametersWithRandom(
- PrivateKeyParameter<ECPrivateKey>(privateKey), _getSecureRandom());
- signer.init(true, params);
-
- var sig = signer.generateSignature(dataToSign) as ECSignature;
-
- return sig;
- }
-
- ///
- /// Verifying the given [signedData] with the given [publicKey] and the given [signature].
- /// Will return **true** if the given [signature] matches the [signedData].
- ///
- /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are :
- ///
- /// * SHA-1/ECDSA
- /// * SHA-224/ECDSA
- /// * SHA-256/ECDSA
- /// * SHA-384/ECDSA
- /// * SHA-512/ECDSA
- /// * SHA-1/DET-ECDSA
- /// * SHA-224/DET-ECDSA
- /// * SHA-256/DET-ECDSA
- /// * SHA-384/DET-ECDSA
- /// * SHA-512/DET-ECDSA
- ///
- static bool ecVerify(
- ECPublicKey publicKey, Uint8List signedData, ECSignature signature,
- {String algorithm = 'SHA-1/ECDSA'}) {
- final verifier = Signer(algorithm) as ECDSASigner;
-
- verifier.init(false, PublicKeyParameter<ECPublicKey>(publicKey));
-
- try {
- return verifier.verifySignature(signedData, signature);
- } on ArgumentError {
- return false;
- }
- }
-
- ///
- /// Returns the modulus of the given [pem] that represents an RSA private key.
- ///
- /// This equals the following openssl command:
- /// ```
- /// openssl rsa -noout -modulus -in FILE.key
- /// ```
- ///
- static BigInt getModulusFromRSAPrivateKeyPem(String pem) {
- RSAPrivateKey privateKey;
- switch (_getPrivateKeyType(pem)) {
- case 'RSA':
- privateKey = rsaPrivateKeyFromPem(pem);
- return privateKey.modulus!;
- case 'RSA_PKCS1':
- privateKey = rsaPrivateKeyFromPemPkcs1(pem);
- return privateKey.modulus!;
- case 'ECC':
- throw ArgumentError('ECC private key not supported.');
- default:
- privateKey = rsaPrivateKeyFromPem(pem);
- return privateKey.modulus!;
- }
- }
-
- ///
- /// Returns the private key type of the given [pem]
- ///
- static String _getPrivateKeyType(String pem) {
- if (pem.startsWith(BEGIN_RSA_PRIVATE_KEY)) {
- return 'RSA_PKCS1';
- } else if (pem.startsWith(BEGIN_PRIVATE_KEY)) {
- return 'RSA';
- } else if (pem.startsWith(BEGIN_EC_PRIVATE_KEY)) {
- return 'ECC';
- }
- return 'RSA';
- }
- }
|