How to extract RSA public key from X509 Certificate - rust

I am new to Rust and I trying to extract RSA public key from a X509 Certificate. Basically, I have a cert string <-----BEGIN CERTIFICATE-----MII......-----END CERTIFICATE----->. I want to extract - <----BEGIN RSA PUBLIC KEY----MIII......----END RSA PUBLIC KEY> from the cert string.
I tried using the pem crate and x509_certificate crate but the key is coming out in an invalid format.
let mut certStr: String = "-----BEGIN CERTIFICATE-----\n".to_owned();
certStr.push_str(&test.certificate);
certStr.push_str("\n-----END CERTIFICATE-----");
let rsa_cert = &X509Certificate::from_pem(certStr.as_bytes()).unwrap();
let pKey = &X509Certificate::public_key_data(&rsa_cert); // Invalid format Key
let pem = parse(&certStr).unwrap(); // This has 2 properties -> tag and contents. Not sure how to extract p
--Edit--
Thanks everyone for your help. I am trying to avoid taking dependency on openssl if possible. All I need is to get the public key out of the certificate to validate a JWT token which is encrypted by a corresponding private key. What would be the secure and straightforward way of doing that in Rust?

You can alternatively use the openssl crate that supports all the tasks you need: the import of the PEM certificate, the extraction of the public key and the export as PEM encoded public key in PKCS#1 format (as well as in various other formats and encodings):
use openssl::x509::X509;
use std::str;
fn main() {
let cert_b64_der = "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";
// Convert certificate to PEM, import PEM certificate
let mut cert_pem: String = "-----BEGIN CERTIFICATE-----\n".to_owned();
cert_pem.push_str(&cert_b64_der);
cert_pem.push_str("\n-----END CERTIFICATE-----");
let cert = &X509::from_pem(cert_pem.as_bytes()).unwrap();
// Pick public key
let public_pkey = cert.public_key().unwrap();
// Export public key as PEM encoded public key in PKCS#1 format
let public_pkcs1_pem = public_pkey.rsa().unwrap().public_key_to_pem_pkcs1().unwrap();
println!("PEM encoded PKCS#1 key:\n{}", str::from_utf8(public_pkcs1_pem.as_slice()).unwrap());
}
As it seems that your certificate is available as a Base64 encoded ASN.1/DER encoded certificate, it is probably more convenient to import this directly as DER and spare the detour via the PEM encoding. For this, simply replace the Convert certificate to PEM code block with:
use base64::{engine::general_purpose, Engine as _};
...
// Convert certificate to DER, import DER certificate
let cert_der = general_purpose::STANDARD.decode(&cert_b64_der).unwrap();
let cert = &X509::from_der(cert_der.as_slice()).unwrap();
That the exported public key really corresponds to the key from the certificate and that the key is correctly formatted can be checked most easily with an ASN.1 parser, e.g. https://lapo.it/asn1js/.
Edit: In the original code in the question the wrong method is used when exporting the key: Instead of the parse() method, the encode() method must be applied. This will return a PEM encoded public key in PKCS#1 format:
use x509_certificate::certificate::X509Certificate;
use pem::{Pem, encode};
use base64::{engine::general_purpose, Engine as _};
fn main() {
let cert_b64_der = "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";
// Convert certificate to PEM, import PEM certifciate
let mut cert_pem: String = "-----BEGIN CERTIFICATE-----\n".to_owned();
cert_pem.push_str(&cert_b64_der);
cert_pem.push_str("\n-----END CERTIFICATE-----");
let cert = &X509Certificate::from_pem(cert_pem.as_bytes()).unwrap();
// Pick public key
let public_key = &X509Certificate::public_key_data(&cert); // Invalid format Key
let public_pkcs1_der = general_purpose::STANDARD.encode(&public_key);
// Export public key as PEM encoded public key in PKCS#1 format
let pem = Pem {
tag: String::from("RSA PUBLIC KEY"),
contents: public_key.to_vec(),
};
let public_pkcs1_pem = encode(&pem);
println!("Base64 encoded DER encoded PKCS#1 key:\n{}", public_pkcs1_der);
println!("PEM encoded PKCS#1 key:\n{}", public_pkcs1_pem);
}
Note that 1. the key returned by public_key_data() is a DER encoded public key in PKCS#1 format and that 2. similar to the openssl library, an ASN.1/DER encoded certificate can also be imported directly here with from_der().

Related

Nodejs construct the public key using public key string

I have a public key string as follows
let pk_str = "public key strig here" and I am using the library jose to verify a JWS
(async() => {
const decoder = new TextDecoder();
const jws = vc_proof_value;
const { payload, protectedHeader } = await compactVerify(jws, pk_str);
console.log(protectedHeader)
console.log(decoder.decode(payload))
})();
I am getting the following error when trying to run the script
(node:75986) UnhandledPromiseRejectionWarning: TypeError: Key must be one of type KeyObject, CryptoKey, or Uint8Array. Received type string
Is there a way to construct the key ?
In NodeJS (I refer to NodeJS, since you have tagged this), the public key is passed as KeyObject wich is created with crypto.createPublicKey(). You didn't say much about the key, presumably it's PEM encoded (since it's a string). In this case, you simply have to pass the PEM encoded key:
var key = crypto.createPublicKey(pk_str);
If in the compactVerify() call pk_str is replaced by key, verification works.
In addition to PEM keys (default), JWK and DER keys (X.509/SPKI or PKCS#1) are also supported.
Here is the documentation for the key argument of all applicable functions.

Reading public and private key from stored files in node js

I wanted to encrypt and decrypt a message in node using public and private keys stored in my system. I was using the following java code to read the file and use the keys.
Java Code:
byte[] keyBytes = Files.readAllBytes(new File(publicKeyFileName).toPath());
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey=kf.generatePublic(spec);
I am able to use the above java method without any issues to read the public key from file. However, I want to achieve similar functionality in node.
I have tried using crypto for achieving the same but it gives me error while passing the key to publicEncrypt method.
Node:
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
var publicKey = fs.read(absolutepath, "utf-8");
console.log(publicKey);
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
Error
internal/crypto/cipher.js:43
return method(toBuf(key), buffer, padding, passphrase);
^
Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
Please help. Thanks
Your problem is located in the file format you are actually using with Java. You probably save the
private and the public in encoded ("byte array") to a file and rebuild the keys e.g. with
X509EncodedKeySpec.
This format is not compatible to Node.JS and you have 3 ways to solve it:
a) you write the keys in Java with re neccessary format for usage in Node.JS
b) you write a converter in Node.JS to get the correct format
c) you convert the files with a tool like OPENSSL.
Here I show you the "c-way" as you are handling just one keypair and probably don't need a programatically solution.
Let's say you have two files with the private key ("rsa_privatekey_2048.der") and the public key ("rsa_publickey_2048.der").
In OPENSSL you are using the command line with
openssl rsa -inform der -in rsa_privatekey_2048.der -outform pem -out rsa_privatekey_2048.pem
openssl rsa -inform der -pubin -in rsa_publickey_2048.der -outform pem -RSAPublicKey_out -out rsa_publickey_2048.pem
to convert the files to their PEM-encoded formats.
Below you can find the two sample files I created.
rsa_privatekey_2048.pem:
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAmbeKgSAwVe0nZ84XlbDhMkUDjx1C0duA16MkzHTg1uh9SouO
KK0e3gPtTJ9LssaHlXSYhjpMDMWGO6ujd85XRosI2u9eSMNRYY25AQuBriSTVdi9
BHqWAuWuo6VuvTrkgWTL69vNWvLXTOkTiIyrgnhiavjNvm4UVy2AcO2Y3ER+dKgJ
pQAYlEP1jvuQuf6dfNdSBoN0DZbxZXYbQqoA9R/u0GZHCXY+r8A54RejG34pnnuH
koyROZz5H9LbKGOiaETryornQ1TRvB/p9tgIoCJFI71WsKsqeWQPG3Ymg/FoEWXN
Y0yopZEjpkZa3tU+hrOmAFIRg+/bedKfjYFi/QIDAQABAoIBAD3XZ3N3fbq0BExw
z3A7jv3oYfwrq3w+MOGQEvfmdaZANlfNOU4ICAkNz2QqGgw8bsOj+tDVl070EILl
FIjYjKgmu1NJRcdEPPNgTvOqq2th75xz6+dnYf6cZNwVbC3ZCaE86gVjkoRqek/I
3UDsRvvgbsfWfP+Fzc0c0zWbgQnsK6qivU1uzJX+5xsvgQlZboeZOO2lsdQMgfnu
iGlW1bVVM4Sy7AngqfiKMzihUnYEBIi0Y+mfxAPcBLUW8mrOvIOPPuNNUPxUtkBF
bDEzZ6loXCLLD8UBqXeDbCUPPFdTGcc7INhVgFdl2FL6rHB0+p6eUt8MI/XkZI2d
2AnkBUkCgYEA34cKLs2l5bPjyKybbj6ZG7RhDRsnPypEGU63DMV21twISqt7ZQNv
i3iTP+FYHM3ImECbNRIOZpyLuWLPmh5+5egQH13jRDempoxVSVcghbIserlCz2EU
nD2V6ZKuaDbn395O6Qe/PE/yKHLWbXwJrBBm+o7GGNm/Jd3KJib23PcCgYEAsAxB
esEsxxL8hqg/qf+ij7JJt492svpK/6QXhqeeP/FVOtYSgoNtSrXh9qahGpzMSF0m
FqwIgrOX0RkK3v6ofGVfIKObxOVyhwddS1Ru+SnjBFnTMKS57q0WNrIrBNM6Q0xE
Wd3tiljwmg8xF90U/BXu+m0v5XWKxSn7VLiCBqsCgYEAgl0xtSY/EP6fZJQ2ek+L
4DqNN6WUeCRgXxonbA1mR91AALyOVNVyIreJuYHlb7ccvJ9BZexH9dRrMQ3N4ibS
/6cecAzD1S9XxF6oBwQHdbH6ewC9VFFcQdsxKW5gxWrwRQJUp1fbUoOVyb1gDa5/
vZg7VvoZ0rh74Mu/cAzdgPUCgYEAiNANdwt29AKyUyefykpLGCczGL8aPP88l6z7
R38uAX1Ygg/pdJoUrnHo+FkIbHkcXMRfHFF3j7NoMWynwSLg50OUiPX80SiLN5qm
iytDzskZjsEL2gq6IF1NHRabTfWlmrVDjR9mQhTabq+NtIDwlPOqs9100nrlbFIy
6uU0z18CgYEAkDxQg5UjOzbYIighp/e0LkfKlwUE/cMtISUq1A6Yio9AKZYdBV8p
dd4osUW0gZvDflXlQaBIaNlOwl035lyEZtyuKmFh1oSmoir/TTMbIk0avSgKCLGg
fnhabaQRHl4RdXWcEsioRv3aZUsGkb46Y8xODyAUPRHBPhBsd71gnZ8=
-----END RSA PRIVATE KEY-----
rsa_publickey_2048.pem:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAmbeKgSAwVe0nZ84XlbDhMkUDjx1C0duA16MkzHTg1uh9SouOKK0e
3gPtTJ9LssaHlXSYhjpMDMWGO6ujd85XRosI2u9eSMNRYY25AQuBriSTVdi9BHqW
AuWuo6VuvTrkgWTL69vNWvLXTOkTiIyrgnhiavjNvm4UVy2AcO2Y3ER+dKgJpQAY
lEP1jvuQuf6dfNdSBoN0DZbxZXYbQqoA9R/u0GZHCXY+r8A54RejG34pnnuHkoyR
OZz5H9LbKGOiaETryornQ1TRvB/p9tgIoCJFI71WsKsqeWQPG3Ymg/FoEWXNY0yo
pZEjpkZa3tU+hrOmAFIRg+/bedKfjYFi/QIDAQAB
-----END RSA PUBLIC KEY-----
There's potentially a few issues with your code or the encryption key you're using:
You're using fs.read incorrectly as Node is asynchronous and it needs a callback function to properly read the file.
The encryption key you're using is formatted incorrectly for crypto.publicEncrypt. You must have the proper RSA headers.
I modified your code to use fs.readFile properly instead in the standard Node callback form, and here's an example encryption key in the correct RSA format to use:
var path = require('path');
var crypto = require('crypto');
var fs = require('fs');
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey, callback) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
fs.readFile(absolutePath, 'utf-8', (err, publicKey) => {
// The value of `publicKey` is in the callback, not the return value
console.log(publicKey);
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
if (err) {
callback(err);
} else {
callback(null, encrypted.toString("base64"));
}
});
};
encryptStringWithRsaPublicKey('hello world', 'test.pub', (err, encrypted) => {
// If you're using a callback in a function,
// the original function must have a callback as well
console.log(encrypted);
});
Example encryption key at test.pub (must have the RSA headers as shown below):
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu
ERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A
+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw
mwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN
QSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P
+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB
-----END RSA PUBLIC KEY-----
As of 2020, there are also other ways of making the code cleaner, such as with using the Promises version of the fs module and async / await, though I wanted to keep this answer as simple as possible for now.

How to generate a PEM-formatted Key from a 64Byte raw hex-formatted Key

I have the following problem:
After recreating the public key from a signed transaction, I try to encrypt some payload with it.
However the node.js-module named "crypto" is expecting a pem-formatted key in the publicEncrypt(key, payload) function.
My Question:
How can I create the pem-formatted key from a raw hex-encoded key?
Here is the recreated 64 Byte public key:
9f9f445051e788461952124dc08647035c0b31d51f6b4653485723f04c9837adb275d41731309f6125c14ea1546d86a27158eec4164c00bab4724eed925e9c60
Information:
I know, that a pem-format-key consists of base64 encoded data, a header and a footer.
-----BEGIN RSA PUBLIC KEY-----
BASE64 ENCODED DATA
-----END RSA PUBLIC KEY-----
I have also found out that within the base64 encoded data the following DER-structure is present:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
So the only question is how to get from the raw hex-encoded key to this DER-structure.
I would appreciate any help!
Problem solved
Thanks to Maarten Bodewes and his comment regarding the key being secp256k1 and not RSA.
After some further research, I finally managed to encrypt/decrypt a message asymmetrically with secp256k1 keys.
With the help of Cryptos ECDH class I managed to create a key-object and then assign the private key to it. When assigned, you can easily derive the public key with getPublicKey(). All participants would create a key object for themselves and assign their private keys to it. Then they share their retrieved public keys (in my case over a shared medium). In addition I used a npm-package named standard-ecies which provides the ECIES encryption-scheme.
Code:
const crypto = require('crypto');
const ecies = require('standard-ecies');
var buffer = new Buffer("Hello World");
var ecdh = crypto.createECDH('secp256k1');
ecdh.setPrivateKey(privateKey);
var encryptedText = ecies.encrypt(ecdh.getPublicKey(), buffer);
var decryptedText = new Buffer(ecies.decrypt(ecdh, encryptedText));
I should have noticed this, because crypto's encryption function (link to the api-doc) clearly works only with RSA keys and not with secp256k1 keys.
Anyway if someone has a similar issue, I hope this answer helps!

Nodejs Generate a keys to sign a piece of data

I have some blob data for example:
const buffer = Buffer.from('ACDFF12BA','hex');
How I can generate keys in order to sign it?
One way to do that is by using the keypair npm package:
const keypair = require('keypair');
const crypto = require('crypto');
const pair = keypair();
// Create Transmitted Signature
const sign = crypto.createSign('RSA-SHA256');
sign.update('abcdef'); // data from your file would go here
const sig = sign.sign(pair.private, 'hex');
console.log(sig);
// Verifying Signature
const verify = crypto.createVerify('RSA-SHA256');
verify.write('abcdef');
verify.end();
console.log(verify.verify(pair.public, sig,'hex'));
As you can see using the keypair() function I generate the public and the private keys via a sign.sign I generate the signature. Keep in mind to sign with a Private Key. The generated the public key in the following format:
--- RSA BEGIN PUBLIC KEY ---
^some base64 here^
--- RSA END PUBLIC KEY ---
And the generated private key will have the same format:
--- RSA BEGIN PRIVATE KEY ---
^some base64 here^
--- RSA END PRIVATE KEY ---
Now the only thing to do is to stringify the buffer as a hex string and do the job. Keep in mind that the receiving end needs to verify the data as hex string as well.
We can verify the signature with the verify.verify and a public key. Keep in mind on the signature's format as well. In case you generate a hex encoded signature then verify it in the same format.
Also the methodology above is recommended when the native crypto.generateKeyPair is not available (eg. in electron applications)

How to import PKCS1 keys from a PEM file containing Private / Public keys in .Net Core

I am trying to load the Private and Public keys from a PEM file using .Net Core.
My code looks like this:
var localPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var path = Path.Combine(localPath, this._configManager.JwtPem);
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
var linesList = File.ReadAllLines(path).ToList();
var line = string.Concat(linesList.GetRange(1, linesList.Count - 2));
rsaCryptoServiceProvider.ImportCspBlob(Convert.FromBase64String(line));
The exception I am getting is:
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException : Bad Version of provider
at Internal.NativeCrypto.CapiHelper.ImportKeyBlob(SafeProvHandle saveProvHandle, CspProviderFlags flags, Boolean addNoSaltFlag, Byte[] keyBlob, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.ImportCspBlob(Byte[] keyBlob)
at StepNexusCA.ServiceLayer.Authorization.TokenService.GenerateToken(List`1 claims)
The PEM file containing the PKCS1 format of my development Private/Public keys is here:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwgs8kmIwk+4geRO7dGZjzYpgD2OiaUrnOOIk+ObXt/CcjhwX
lSst+jBmfMF1Wp/mF4aUQsePxN59MYV2BsqPLEkzVdq/fb/7V2wbZcooJAQKkJwT
emtYHrBN00KBBeu9uQZlFOw365ij4GrbP7mcr4tNFZ3TPnRFUUFqhvB6mEG1aZsb
lOn1lgL34tAycQHNxttXz/aGfPyTefQ+yISvSY2n8288OVlyfu6wKDONQYS+/stC
tCV+a+/dDUSUjaZsXM1+BMSflsINqIcCTCMvPa6fb5Z+USfPDcDNwzUyX20LBzH5
wFwPLIvuoqJeeczcaHaT+dS2ZZREj6kgUsdC+QIBAwKCAQEAgVzTDEF1t/QVpg0n
ou7tM7GVX5fBm4dE0JbDUJnlJUsTCWgPuMdz/CBEUyujkb/uulm4LIUKgz7+IQOk
BIcKHYYiOTx/qSqnj51nmTFwGAKxtb1iUZzlacrejNcArp0pJgRDY0glR7sX6vHn
f9ETH7IzY76M1E2Di4Dxr0r8ZX/3ozsrSXp+GMJLeN9sCjKSyxoE5Y71eDBTCX2N
tShJJjhqUDz61bhKlX9j5c3jWvTXx46dE8wjoJ/BW1XJo5J1gzHQ/OLYeOXIdxlj
jVSlEuU69UT588B7UEEK9N9xK5K/c0Yw5gd02RUv/o7qdpYQICeGtQMMaFkm75xy
nUOxwwKBgQD/orUvgNJfFKyvGY8XJTuek5q8IcFD8AFO3b7pNnPynw8llyEpACAv
Onf9aJSPZvtrabSqrpO8k8Ijyhe2Ino39GuRV8RURl46GmFN31RoYV1wHI4K7Emh
68cdKbCEBudog+kImImldBAfo+QmBtqhS+u4B5qQwwnFa8DriQoiYwKBgQDCUg0r
Jd/ZXDLXk/H5PHpTApmUVd7SWLLIDfkBAlRO8Sni4/Ka+KTTZDec5uoo0hoP6cCs
Z9+MZz4XOiwv9dCEI5czMawGmwsm23+fGM/PP/lW4yD8dz10KZggKjWElymDVl+n
zsc6ctwHAOfYwREi7E+R4rWTBgTEvH2I3deV8wKBgQCqbHjKVeGUuHMfZl9kw30U
YmcoFoDX9VY0k9SbeaKhv19uZMDGABV00aVTmw2071JHm83HHw0oYoFtMWUkFvwl
TZ0Lj9g4Lul8EZYz6jhFlj5KvbQHSDEWnS9oxnWtWe+bAptbEFvDorVqbULEBJHA
3UfQBRG111vY8oCdBgbBlwKBgQCBjAjHbpU7ksyPt/amKFGMrGZi4+nhkHcwCVCr
VuLfS3FB7UxnUG3iQs+970bF4Wa1RoBy7+pdmilk0XLKo+BYF7oiIR1ZvLIZ56pq
EIqKKqY57MCoT35NcRAVcXkDD3ECOZUaidom9z1aAJqQgLYXSDUL7HkMrq3YfakF
6Tpj9wKBgEPCSW7EMFjK2NzmB+4b+skxXcfCZ0ldNtwoUDijuAMFg8ueC3j2qFUX
bAXSApi3mQMow1/JwQxiZ+b+GDLdTcE/PrBVBRkL/5RkmnVagbjBrdZhVjpC+dUo
eEkCChClGGpRyPJ+DYYRyX1Fk9Und8Xbd49Vv+/6RL76ys3gGQl8
-----END RSA PRIVATE KEY-----
Why can't I import the key using ImportCspBlob(...)? I have not found much info online regarding the exception but where is my code wrong? I am aware of BouncyCastle but I am trying to do this natively using .Net Core.
The format for ImportCspBlob is the format from ExportCspBlob, which is the PRIVATEKEY blob format required by CryptImportKey. Since .NET just transparently passes that on to Windows CAPI, the ImportCspBlob method throws on non-Windows platforms.
Another answer that I've given in the past for importing private keys (including PKCS#1 RSAPrivateKey) is a bit of a meta-answer, which includes links to just get things working: Digital signature in c# without using BouncyCastle.
.NET Core 3.0's daily builds have the functionality built-in. Mostly. The PEM format is easy in practice, but somewhat annoying in the spec, so the methods leave it up to the caller to "un-PEM" the data... for the default formatting on a single-value payload with no attributes (like you have in your example) you can do it with daily builds via
private static RSA ReadKeyFromFile(string filename)
{
string pemContents = System.IO.File.ReadAllText(filename);
const string RsaPrivateKeyHeader = "-----BEGIN RSA PRIVATE KEY-----";
const string RsaPrivateKeyFooter = "-----END RSA PRIVATE KEY-----";
if (pemContents.StartsWith(RsaPrivateKeyHeader))
{
int endIdx = pemContents.IndexOf(
RsaPrivateKeyFooter,
RsaPrivateKeyHeader.Length,
StringComparison.Ordinal);
string base64 = pemContents.Substring(
RsaPrivateKeyHeader.Length,
endIdx - RsaPrivateKeyHeader.Length);
byte[] der = Convert.FromBase64String(base64);
RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(der, out _);
return rsa;
}
// "BEGIN PRIVATE KEY" (ImportPkcs8PrivateKey),
// "BEGIN ENCRYPTED PRIVATE KEY" (ImportEncryptedPkcs8PrivateKey),
// "BEGIN PUBLIC KEY" (ImportSubjectPublicKeyInfo),
// "BEGIN RSA PUBLIC KEY" (ImportRSAPublicKey)
// could any/all be handled here.
throw new InvalidOperationException();
}
Daily builds of the .NET Core SDK can be obtained from https://github.com/dotnet/core-sdk/#installers-and-binaries
There are a lot of Convert PEM to XML online tools, just convert your pem to xml, then
RSA.FromXmlString(string xmlString)
If you don't need to convert from PEM to DER in the code, you can use openssl to get the DER encoded private key file:
openssl rsa -in key.pem -out key.der -outform der
The .Net Cryptographic API does not support the industry widely used PEM files so we need to convert it to the XML format, introduced by Microsoft. Basically, the solution was found in another similar question here C# Extract public key from RSA PEM private key.

Resources