|
@ -0,0 +1,979 @@ |
|
|
|
|
|
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'; |
|
|
|
|
|
} |
|
|
|
|
|
} |