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 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(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 = []; 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 . /// /// ``` /// 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 . /// /// ``` /// 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 . /// ``` /// 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 /// /// ```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 /// /// ```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 () and PKCS8 () /// 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(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(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(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(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(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(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'; } }