Skip to content

Instantly share code, notes, and snippets.

@proteye
Last active March 19, 2024 08:59
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save proteye/982d9991922276ccfb011dfc55443d74 to your computer and use it in GitHub Desktop.
Save proteye/982d9991922276ccfb011dfc55443d74 to your computer and use it in GitHub Desktop.
How to encode/decode RSA private/public keys to PEM format in Dart with asn1lib and pointycastle
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import "package:pointycastle/export.dart";
import "package:asn1lib/asn1lib.dart";
List<int> decodePEM(String pem) {
var startsWith = [
"-----BEGIN PUBLIC KEY-----",
"-----BEGIN PRIVATE KEY-----",
"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
"-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
];
var endsWith = [
"-----END PUBLIC KEY-----",
"-----END PRIVATE KEY-----",
"-----END PGP PUBLIC KEY BLOCK-----",
"-----END PGP PRIVATE KEY BLOCK-----",
];
bool isOpenPgp = pem.indexOf('BEGIN PGP') != -1;
for (var s in startsWith) {
if (pem.startsWith(s)) {
pem = pem.substring(s.length);
}
}
for (var s in endsWith) {
if (pem.endsWith(s)) {
pem = pem.substring(0, pem.length - s.length);
}
}
if (isOpenPgp) {
var index = pem.indexOf('\r\n');
pem = pem.substring(0, index);
}
pem = pem.replaceAll('\n', '');
pem = pem.replaceAll('\r', '');
return base64.decode(pem);
}
class RsaKeyHelper {
AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateKeyPair() {
var keyParams = new RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 12);
var secureRandom = new FortunaRandom();
var random = new Random.secure();
List<int> seeds = [];
for (int i = 0; i < 32; i++) {
seeds.add(random.nextInt(255));
}
secureRandom.seed(new KeyParameter(new Uint8List.fromList(seeds)));
var rngParams = new ParametersWithRandom(keyParams, secureRandom);
var k = new RSAKeyGenerator();
k.init(rngParams);
return k.generateKeyPair();
}
String encrypt(String plaintext, RSAPublicKey publicKey) {
var cipher = new RSAEngine()
..init(true, new PublicKeyParameter<RSAPublicKey>(publicKey));
var cipherText = cipher.process(new Uint8List.fromList(plaintext.codeUnits));
return new String.fromCharCodes(cipherText);
}
String decrypt(String ciphertext, RSAPrivateKey privateKey) {
var cipher = new RSAEngine()
..init(false, new PrivateKeyParameter<RSAPrivateKey>(privateKey));
var decrypted = cipher.process(new Uint8List.fromList(ciphertext.codeUnits));
return new String.fromCharCodes(decrypted);
}
parsePublicKeyFromPem(pemString) {
List<int> publicKeyDER = decodePEM(pemString);
var asn1Parser = new ASN1Parser(publicKeyDER);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var publicKeyBitString = topLevelSeq.elements[1];
var publicKeyAsn = new ASN1Parser(publicKeyBitString.contentBytes());
ASN1Sequence publicKeySeq = publicKeyAsn.nextObject();
var modulus = publicKeySeq.elements[0] as ASN1Integer;
var exponent = publicKeySeq.elements[1] as ASN1Integer;
RSAPublicKey rsaPublicKey = RSAPublicKey(
modulus.valueAsBigInteger,
exponent.valueAsBigInteger
);
return rsaPublicKey;
}
parsePrivateKeyFromPem(pemString) {
List<int> privateKeyDER = decodePEM(pemString);
var asn1Parser = new ASN1Parser(privateKeyDER);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var version = topLevelSeq.elements[0];
var algorithm = topLevelSeq.elements[1];
var privateKey = topLevelSeq.elements[2];
asn1Parser = new ASN1Parser(privateKey.contentBytes());
var pkSeq = asn1Parser.nextObject() as ASN1Sequence;
version = pkSeq.elements[0];
var modulus = pkSeq.elements[1] as ASN1Integer;
var 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;
var exp1 = pkSeq.elements[6] as ASN1Integer;
var exp2 = pkSeq.elements[7] as ASN1Integer;
var co = pkSeq.elements[8] as ASN1Integer;
RSAPrivateKey rsaPrivateKey = RSAPrivateKey(
modulus.valueAsBigInteger,
privateExponent.valueAsBigInteger,
p.valueAsBigInteger,
q.valueAsBigInteger
);
return rsaPrivateKey;
}
encodePublicKeyToPem(RSAPublicKey publicKey) {
var algorithmSeq = new ASN1Sequence();
var algorithmAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
var paramsAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(algorithmAsn1Obj);
algorithmSeq.add(paramsAsn1Obj);
var publicKeySeq = new ASN1Sequence();
publicKeySeq.add(ASN1Integer(publicKey.modulus));
publicKeySeq.add(ASN1Integer(publicKey.exponent));
var publicKeySeqBitString = new ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes));
var topLevelSeq = new ASN1Sequence();
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeySeqBitString);
var dataBase64 = base64.encode(topLevelSeq.encodedBytes);
return """-----BEGIN PUBLIC KEY-----\r\n$dataBase64\r\n-----END PUBLIC KEY-----""";
}
encodePrivateKeyToPem(RSAPrivateKey privateKey) {
var version = ASN1Integer(BigInt.from(0));
var algorithmSeq = new ASN1Sequence();
var algorithmAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1]));
var paramsAsn1Obj = new ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(algorithmAsn1Obj);
algorithmSeq.add(paramsAsn1Obj);
var privateKeySeq = new ASN1Sequence();
var modulus = ASN1Integer(privateKey.n);
var publicExponent = ASN1Integer(BigInt.parse('65537'));
var privateExponent = ASN1Integer(privateKey.d);
var p = ASN1Integer(privateKey.p);
var q = ASN1Integer(privateKey.q);
var dP = privateKey.d % (privateKey.p - BigInt.from(1));
var exp1 = ASN1Integer(dP);
var dQ = privateKey.d % (privateKey.q - BigInt.from(1));
var exp2 = ASN1Integer(dQ);
var iQ = privateKey.q.modInverse(privateKey.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 = new ASN1OctetString(Uint8List.fromList(privateKeySeq.encodedBytes));
var topLevelSeq = new ASN1Sequence();
topLevelSeq.add(version);
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeySeqOctetString);
var dataBase64 = base64.encode(topLevelSeq.encodedBytes);
return """-----BEGIN PRIVATE KEY-----\r\n$dataBase64\r\n-----END PRIVATE KEY-----""";
}
}
@1AlexFix1
Copy link

1AlexFix1 commented Jan 15, 2019

Hi, thanks for the work, it saved me a lot of time, I have a few additions.

  1. Worth adding in _decodePem
    "----- BEGIN RSA PRIVATE KEY -----"
    and "----- END RSA PRIVATE KEY -----"
    With these headers, keys are mainly generated, for example, on android, ios packages from the box for generation add these headers. For public keys, it’s also worth adding
    ----- BEGIN RSA PUBLIC KEY -----
    ----- END RSA PUBLIC KEY -----
    because the native iOS key generator generates a public key with these headers

  2. In accordance with this standard when converting to pem format, the line should be divided by 64 characters each, in some libraries this causes the ability to not read the key.
    Solution to the forehead

 var dataBase64 =
        _castToPrintableEncodingPem(base64.encode(topLevelSeq.encodedBytes));
   

String _castToPrintableEncodingPem(String key) {
    var temp = '';
    for (int i = 0; i < key.length; i++) {
      if (i != 0 && i % 64 == 0) temp += '\n';
      temp += key[i];
    }
    return temp;
  }

@guoyuanzhuang
Copy link

Hi, thanks for the work, it saved me a lot of time, I have a few additions.
RsaKeyHelper().parsePublicKeyFromPem(certFile.trim()); An error was reported using this method.
Unhandled exception: type 'ASN1ObjectIdentifier' is not a subtype of type 'ASN1Sequence' #0 RsaKeyHelper.parsePublicKeyFromPem (package:flutter_app/utils/rsa_pem.dart:106:18) #1 main (package:flutter_app/utils/encryptUtils.dart:80:43) #2 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:289:19) #3 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)
Here's my pem:
-----BEGIN CERTIFICATE----- MIIDQzCCAiugAwIBAgIEM6flhTANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJ6 aDELMAkGA1UECBMCaHoxCzAJBgNVBAcTAmh6MQ0wCwYDVQQKEwRkZHNjMQ0wCwYD VQQLEwRkZHNjMQswCQYDVQQDEwJsaTAeFw0xNzA0MjAwNzI4NDVaFw0xNzA3MTkw NzI4NDVaMFIxCzAJBgNVBAYTAnpoMQswCQYDVQQIEwJoejELMAkGA1UEBxMCaHox DTALBgNVBAoTBGRkc2MxDTALBgNVBAsTBGRkc2MxCzAJBgNVBAMTAmxpMIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmG0F8Q5hg67kRSm1etU1m8ruWb9E kal1bsVUAdWNPc+391ycRYqFt3WfW59vOX4aUC2q1yygJCq+LlMZKCV7FgiqAhMT 56PFcCLp9E8HXjx1xfp38T/gItSWX5JWBY8bKjSgAHj81/F4lopPJs1b/p7tph3n KFSHcWOyi0svQa7hPMpLZtYuwAGwNnB54+TorXMcNgJoAmy3VULTP5orJq2r3drK JJdFakRM0IYlBn09gpE3szQKMlCF9/Ao23LXVG43kNDWvyU1vJe9ObYWrB6JCe2K 6kSVobr0vsTyPn/xtKGaEQfv4mC0aPf1eVJp3o1WIbu+PLYyZiBMZqfHhQIDAQAB oyEwHzAdBgNVHQ4EFgQUawZr7dWmL7f2gIz20Pcl/0TKdp4wDQYJKoZIhvcNAQEL BQADggEBAExNe354GXt03oTNix9o7GG0Xx7ozK3hTjm6aW895JIPiSxh5Fj1arqu xidjOW4v0QT2oyhdsxNMr65Q6aXSBr+u3O30iPZRWRBIcWezUhq46067Wo+HYx1u mE5kiEkZ+pjTxWEZtOncOTuhlnsCEAQ28Jb24mwCeTB5eSBM9oKX5crTMXvJO73U /T4D5bg63goiNP61LUF5d3T5kK+0SkxYk+L8e6U1LTaJCEFD0jJEU5JM4nG1N4zX iB88NijY7YlM/F03Ph3GXx52SWTIToI0h6bSbbhKM00974z63w0sFm4WpZcOE0Px 3ugLoSKVaZKa99NCbgQiRNpGsy7ixTo= -----END CERTIFICATE-----

@guoyuanzhuang
Copy link

I finished it in Java now,
If you have time, you can try to solve it
thank you

@MoeinElwani
Copy link

Hello and thank you for this work ,
but , there is an issue on method "parsePublicKeyFromPem() "
Unhandled exception: type 'ASN1ObjectIdentifier' is not a subtype of type 'ASN1Sequence' #0 RsaKeyHelper.parsePublicKeyFromPem
BR

@apdarshan
Copy link

Hello and thank you for this work ,
but , there is an issue on method "parsePublicKeyFromPem() "
Unhandled exception: type 'ASN1ObjectIdentifier' is not a subtype of type 'ASN1Sequence' #0 RsaKeyHelper.parsePublicKeyFromPem
BR

has anyone found fix for this ?

@bwbookbbok
Copy link

parsePrivateKeyFromPem function is helpful , thanks

@mppy1
Copy link

mppy1 commented Jul 28, 2020

Hello
To perform the parsePublicKeyFromPem() method, the parse ECPublicKey, result reported the following error.

Unhandled exception:
RangeError: Value not in range: 2329388466087989709
#0 _rangeCheck (dart:typed_data-patch/typed_data_patch.dart:4631:5)

#1 _ByteBuffer.asUint8List (dart:typed_data-patch/typed_data_patch.dart:1931:5)
#2 new Uint8List.view (dart:typed_data:847:19)
#3 ASN1Parser.nextObject
package:asn1lib/asn1parser.dart:45
#4 parsePublicKeyFromPem
test\signers\test.dart:63
#5 main
test\signers\test.dart:21
#6 _startIsolate. (dart:isolate-patch/isolate_patch.dart:307:19)
#7 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)

@albert0m
Copy link

this code doesn't work anymore with the newer versions of the pointycastle library.

@Andrewcpu
Copy link

New Import:
import 'package:pointycastle/asymmetric/api.dart' as pointy;

Modified generateKeyPair():

AsymmetricKeyPair<pointy.RSAPublicKey, pointy.RSAPrivateKey> generateKeyPair() {
    var keyParams =
        new RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 12);

    var secureRandom = new FortunaRandom();
    var random = new Random.secure();
    List<int> seeds = [];
    for (int i = 0; i < 32; i++) {
      seeds.add(random.nextInt(255));
    }
    secureRandom.seed(new KeyParameter(new Uint8List.fromList(seeds)));

    var rngParams = new ParametersWithRandom(keyParams, secureRandom);
    var k = new RSAKeyGenerator();
    k.init(rngParams);
    AsymmetricKeyPair keyPair = k.generateKeyPair();
    AsymmetricKeyPair<pointy.RSAPublicKey, pointy.RSAPrivateKey> pair =
        AsymmetricKeyPair(keyPair.publicKey as pointy.RSAPublicKey,
            keyPair.privateKey as pointy.RSAPrivateKey);
    return pair;
  }

@vito-go
Copy link

vito-go commented Sep 20, 2022

very very thanks!

@jaysephjw
Copy link

That code is published on pub as the basic_utils in the CryptoUtils class:
https://pub.dev/packages/basic_utils#cryptoutils

Adding the pub dep is the way to go vs copying in code (just update the package to get bugfixes / keep up with api changes etc).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment