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-----""";
}
}
@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