Search code examples
c#asp.net-coreopensslrsaprivate-key

Trying to use an OpenSSL RSA generated key for use in an ASP .NET application


I'm trying to use keys I generated with OpenSSL. Here are the commands I used to create them:

openssl genrsa -out keypair-2023.pem 4096
openssl rsa -in keypair-2023.pem -pubout -out pub-2023.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair-2023.pem -out priv-2023-v8.key

The format of the files are like so...

-----BEGIN PUBLIC KEY-----
{{ public key contents }}
-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----
{{ private key contents }}
-----END PRIVATE KEY-----

Here are a few code snippets I've attempted in order to get them to work (as well as the results)

var rsaKey = RSA.Create();

rsaKey.ImportFromPem(privateKeyStr);
rsaKey.ImportFromPem(publicKeyStr);

var rsaParams = rsaKey.ExportParameters(true);

var securityKey = new RsaSecurityKey(rsaParams);

Result: System.Security.Cryptography.CryptographicException: 'Key does not exist.' at the "ExportParameters" line

var privateKeyBuffer = new Span<byte>(new byte[privateKeyStr.Length]);
Convert.TryFromBase64String(privateKeyStr, privateKeyBuffer, out _);

var rsaKey = RSA.Create();
rsaKey.ImportRSAPrivateKey(privateKeyBuffer, out _);
rsaKey.ImportFromPem(publicKeyStr);

var rsaParams = rsaKey.ExportParameters(true);

Result: AsnContentException: The provided data is tagged with 'Universal' class value '0', but it should have been 'Universal' class value '16' at the "ImportRSAPrivateKey" line.

Swapping the "ImportRSAPrivateKey" method for the "ImportPkcs8PrivateKey" results in the same exception being thrown.

Now, the purpose for these keys is to sign and verify JWT tokens. If I use the first code snippet but I create the "securityKey" using the "rsaKey" object, I have the code below:

var rsaKey = RSA.Create();

rsaKey.ImportFromPem(privateKeyStr);
rsaKey.ImportFromPem(publicKeyStr);


var securityKey = new RsaSecurityKey(rsaKey);
_signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha512);
_securityTokenHandler = new JwtSecurityTokenHandler();

The app launches, but when I reach the code to use the "_signingCredentials" in creating the JWT token, I get this exception: System.Security.Cryptography.CryptographicException: 'Key does not exist.'

Is there a way to use the Keys correctly such that I can create JWT tokens? (I suspect that if I can get past the var rsaParams = rsaKey.ExportParameters(true) line, then JWT token signing should work).


Solution

  • First you import the private key, then you import the public key and thereby overwrite the private key, i.e. in the end only the public key is imported.
    Since the private key is needed for ExportParameters(true) and when signing, but this is missing, the CryptographicException' 'Key does not exist' is thrown.

    Fix: So that the private key is not overwritten, the line rsaKey.ImportFromPem(publicKeyStr) must be removed. With this change, signing can be done and the token can be created successfully.

    The following code does what you want:

    using System;
    using System.Collections.Generic;
    using System.Security.Claims;
    using System.Security.Cryptography;
    using System.IdentityModel.Tokens.Jwt;
    using Microsoft.IdentityModel.Tokens;
    ...
    var rsaKey = RSA.Create();
    rsaKey.ImportFromPem(privateKeyStr);
    //rsaKey.ImportFromPem(publicKeyStr);                       // Fix: remove line
    var rsaParams = rsaKey.ExportParameters(true);              // succeeds now...
    var securityKey = new RsaSecurityKey(rsaKey);
    var _signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha512);
    var jwt = new JwtSecurityToken(new JwtHeader(_signingCredentials),new JwtPayload("issuer", "audience", new List<Claim>(), DateTime.UtcNow, DateTime.UtcNow.AddHours(3)));
    var token = new JwtSecurityTokenHandler().WriteToken(jwt);  // succeeds now...
    

    The second code snippet would fail for the same reason, but throws an exception even before that, since the format of the posted private key is PKCS#8, while ImportRSAPrivateKey() requires a private key in PKCS#1 format.
    Furthermore, ImportRSAPrivateKey() expects a DER encoded key, while the posted keys are PEM encoded (the DER encoded key can be derived from the PEM encoded one by removing header, footer and line breaks and Base64 decoding the remainder).