Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

979 lines
31 KiB

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';
}
}