How to verify JWT token with ES384 algorithm with Nodejs tools signed with JwtSecurityTokenHandler using CNG keys - node.js

I'm trying to verify JWT token with Node.js tools signed with JwtSecurityTokenHandler using CNG generated keys
I tried many Nood.js tools e.g. jsonwebtoken
jwt.verify(token, publickey,{ algorithms: ['ES384'], ...
But get wrong tag errors every time
["error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error"],"library":"asn1 encoding routines","function":"asn1_check_tlen","reason":"wrong tag","code":"ERR_OSSL_ASN1_WRONG_TAG"
The public and private keys generated with CNG
var key = CngKey.Create(CngAlgorithm.ECDsaP384, "keyName",
new CngKeyCreationParameters
{
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
KeyUsage = CngKeyUsages.AllUsages,
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
});
txtPrivateKey = Convert.ToBase64String(key.Export(CngKeyBlobFormat.EccPrivateBlob));
txtPublicKey = Convert.ToBase64String(key.Export(CngKeyBlobFormat.EccPublicBlob));
I tried with converting the keys, but still getting the same exception.
How can I generate a valid public key for Node.js tools using CNG and ES384 algorithm?

It seems I found the solution:
export private key in pkcs8 format
Convert.ToBase64String(key.Export(CngKeyBlobFormat.Pkcs8PrivateBlob));
save into pem and add BEGIN/END PRIVATE KEY
generate public key with openssl
openssl ec -in pkcs8.pem -pubout -out pkcs8genpubkey.pem

Related

node-forge use certificationRequestFromPem to then get the SHA-256 Hash of CSR and MD5 Hash

I am using node-forge, and need to parse a CSR-PEM which can be done with the following method:
Forge.pki.certificationRequestFromPem(csr)
But now i need to get the SHA-256 Hash of the CSR and a MD5 Hash. I have gone through all the examples and documentation but cant find anything.
Can anyone assist with this if they know forge well? Or maybe another nodejs package that can do it?
I just noticed that i actually need the hash from the DER-encoded CSR, so first i need to convert the PEM to DER :-(
You can get the CSR hashes in the following way:
const Forge = require('node-forge');
const csrPem = `-----BEGIN CERTIFICATE REQUEST-----
MIIDKjCCAhICAQAwgYQxFDASBgNVBAMMC2V4YW1wbGUuY29tMRQwEgYDVQQHDAtM
b3MgQW5nZWxlczETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBs
ZSBJbmMxCzAJBgNVBAsMAk5BMREwDwYJKoZIhvcNAQkBFgJOQTELMAkGA1UEBhMC
VVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDwkcOjk1kyo7cJ6oJi
Eh8ziUh+35NdgUXrJWhpb0sWddFYaq+VlwXp1fE9luQRc151zr4lLdGyV26LYGfT
A85S40q9IcSXklB+gQG5O+wdRisI76HMnQ/SFoHKOjsuaH+vosZvWureifuiTqly
kGsw6N+4i+O8RB/vQl9y6y1oLjeUOXxiBQkU97e9GBzXzvwSyXkrujArchRhkpd0
K7US0lkNbfV3As1UQxxkGDWXRHNYKJgjmTOQ0clzwBTL54gcgSGtwxEoVHgzgOGi
13+YTDvxDFKbZGEMnIAe0vHefiRIjXGujF2sbA+tJHw9lmNTleWI3XCwguosYWuY
R8eFAgMBAAGgYDBeBgkqhkiG9w0BCQ4xUTBPMAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAWBgNVHREEDzANggtl
eGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAvGmMT1+5rJ7F5cVDKs551BnH
dXOCTrhJ9gQ/IlYsT9h5IowJzkkaYwRDDy3vuxYCg3GrexhFEHrwQq62X7U46Wrg
U5NIKbFoOIEW18mhhhgeHOBqyNlMmCwZgDD99+O6NRtHAr/hMW5xDjHdcmtJKh0w
aqYyiEpR8YUAqod4pDT20IGdlDksoGP5bqfeIjm3Xqqz5SFj2zZMMA0RdNOibxMU
64cYx+iJ4+tfDg3mHfoR2YIvPz/WhrT0/9iKfh2nGH0xZWge5A28zMaGmEVSrt2a
DSBRNlhZ9XQn3d1W3Om0DiTv9448RuCZsOJSj8iWaDvhCKzei+WaLznFiCKyNA==
-----END CERTIFICATE REQUEST-----`
const csrBytes = Forge.pki.pemToDer(csrPem).getBytes();
const md5 = Forge.md.md5.create();
const sha256 = Forge.md.sha256.create();
md5.update(csrBytes, 'raw')
sha256.update(csrBytes, 'raw')
console.log(md5.digest().toHex().toUpperCase());
console.log(sha256.digest().toHex().toUpperCase());

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.

Creating JWT token with x5c header parameter using System.IdentityModel.Tokens.Jwt

My project is building an authentication service based on .NET Core and the System.IdentityModel.Tokens.Jwt nuget package. We want to create JWT tokens that include the public key certificate (or certificate chain) that can be used to verify the JWT digital signatures. This is possible with commercial identity providers (SaaS), and is supported in the JWT specification by means of a header parameter called "x5c". But I have so far been unable to get this to work using System.IdentityModel.Tokens.Jwt.
I am able to create a JWT token signed using a certificate. The certificate is self-signed and created using openssl (commands included underneath).
My test code in C# looks like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
// more usings..
public static string GenerateJwtToken(int exampleAccountId, string x509CertFilePath, string x509CertFilePassword)
{
var tokenHandler = new JwtSecurityTokenHandler();
var signingCert = new X509Certificate2(x509CertFilePath, x509CertFilePassword);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, exampleAccountId.ToString()) }),
Expires = DateTime.UtcNow.AddDays(30),
Audience = "myapp:1",
Issuer = "self",
SigningCredentials = new X509SigningCredentials(signingCert, SecurityAlgorithms.RsaSha512Signature),
Claims = new Dictionary<string, object>()
{
["test1"] = "hello world",
["test2"] = new List<int> { 1, 2, 4, 9 }
}
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
The generated token header deserializes to this in jwt.io:
{
"alg": "RS512",
"kid": "193A49ED67F22850F4A95258FF07571A985BFCBE",
"x5t": "GTpJ7WfyKFD0qVJY_wdXGphb_L4",
"typ": "JWT"
}
Thing is, I would like to get the "x5c" header parameter output as well. The reason for this is that my project is trying to include the certificate with the public key to validate the token signature inside the token itself, and "x5c" is a good way to do this. But I just cannot get this to work.
I have tried adding x5c manually with AdditionalHeaderClaims on SecurityTokenDescriptor, but it just isn't being output in the token.
Does anybody know how to do this, or can you point me to some solid resources on the subject?
By the way, this is how I generated the certificate used (on Windows):
openssl genrsa -out private2048b.key 2048
openssl req -new -key private2048b.key -out myrequest2048.csr -config <path to openssl.cfg>
openssl x509 -req -days 3650 -in myrequest2048.csr -signkey private2048b.key -out public2048b.crt
openssl pkcs12 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in public2048b.crt -inkey private2048b.key -out mypkcs2048.pfx -name "Testtest"
The PFX is the file being read and used in the code.
Update for posterity
Using Abdulrahman Falyoun's answer, the final part of the code was updated to use token.Header.Add to manually add in the "x5c" header parameter, before serializing the JWT token. Token had to be cast as JwtSecurityToken.
This worked, and created a token that was valid (and had a signature that could immediatly be verified) in https://jwt.io :
// create JwtSecurityTokenHandler and SecurityTokenDescriptor instance before here..
var exportedCertificate = Convert.ToBase64String(signingCert.Export(X509ContentType.Cert, x509CertFilePassword));
// Add x5c header parameter containing the signing certificate:
var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
token.Header.Add(JwtHeaderParameterNames.X5c, new List<string> { exportedCertificate });
return tokenHandler.WriteToken(token);
What is x5c?
The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (not base64url-encoded) DER [ITU.X690.2008] PKIX certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The recipient MUST validate the certificate chain according to RFC 5280 [RFC5280] and consider the certificate or certificate chain to be invalid if any validation failure occurs. The use of this Header Parameter is OPTIONAL.
Note
From the security point of view - do not use the x5c certificate to validate the signature directly. In that case, anybody could just provide their own certificate and spoof any identity.
The purpose of the x5t / x5t#S256 header is to identify the signer - check you trust the certificate provided by x5c or x5t#S256 (or its issuer) under the specified iss, only then you should validate the signature.
so to build the X509 chain
X509Chain chain = new X509Chain()
bool success = chain.Build(cert);
if (!success) throw Error
Then for each chain.ChainElements value, take the Certificate property RawValue property (and base64 encode it).
finally, you got the string for x5c and should only provide it to the headers of jwt.
See the following links
Create JWK Set Containing Certificates
Generate x5c certificate chain from JWK
How to obtain JWKs and use them in JWT signing?
How to get x5c from RSACryptoServiceProvider
Hope it's useful.
#Edit
If the issue was to supply the x5c to the header, you have to add it using
token.Header.Add(name, value)

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!

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