Search code examples
c#cryptographyrsabouncycastle.net-standard

C# How to get an RSA Instance from String of Public and Private Keys?


I'm building my own .NET Standard 2.0 library for DocuSign's API and I need to get an RSA instance from the public and private key strings they generated so I can create the JWT. While I was prototyping this in LINQPad, I had the advantage of using the .NET 7 runtime and the RSA.ImportFromPem(), but that's not available in .NET Standard 2.0.

I tried to come up with something using Bouncy Castle, but it's my first time using the library and I'm not well versed in cryptography.

How can I take a string input of the public or private key and get an RSA instance to use later on for the JWT generation?

The library has to be .NET Standard 2.0 because the project that will be consuming it is on .NET Framework 4.8.

An example of the strings I'm working with:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwvyMB2MwWJ1TG2iTbozZOovSAEeUBg6dpqz+pGhwf0ll3OU1
...
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwvyMB2MwWJ1TG2iTbozZ
...
-----END PUBLIC KEY-----

I also don't want to deal with saving the strings to files, then to read them, then to delete them. The API docs state that the JWT has to be regenerated every 45 minutes, and I'd rather just hold the strings in memory and use them as needed.


Solution

  • The posted private key is a PEM encoded key in PKCS#1 format, the public key is a PEM encoded key in X.509/SPKI format.

    Quite analogous to this post, which however refers to .NET Core 2.2, C#/BouncyCastle can also be used for .NET Framework (especially .NET Standard 2.0) for importing PEM keys:

    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.OpenSsl;
    using Org.BouncyCastle.Security;
    using System.IO;
    using System.Security.Cryptography;
    
    public static RSA ImportFromPem(string pem, bool isPrivate)
    {
        PemReader pemReader = new PemReader(new StringReader(pem));
        object key = pemReader.ReadObject();
    
        RSAParameters rsaParameters = isPrivate ?
            DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)(((AsymmetricCipherKeyPair)key).Private)) :
            DotNetUtilities.ToRSAParameters((RsaKeyParameters)key);
    
        RSA rsa = RSA.Create();
        rsa.ImportParameters(rsaParameters);
    
        return rsa;
    }
    

    using Portable.BouncyCastle for .NET Framework 4, .NET Standard 2.0.


    Usage example:

    // Note: 512-bit keys only for test purposes; nowadays at least 2048-bit keys must be used!
    string privateKeyPkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
                                    MIIBOgIBAAJBALWDYiRs0Q209/tUtdaYP5hvPs76U6U68kJcRntz2anwtvCURhhY
                                    dAZ6GDK8jQOREzlkTGRHxlj9NVb/zNp4PdECAwEAAQJAZ6Di3zjhAZpYGb17M1Eo
                                    vbaFfVWde6/zr79O3hx+IG6/O/OLZIbNd6Zx9+JFrGyLFHxwNFvdaDHK1+H1gqhp
                                    gQIhAN7nvzgc2EFtHyPaffYC23qgzwLabFV+BrjX/sbpao6dAiEA0HZlA78OqWL5
                                    yX92DdfqeXG5U363y7BwQMSKX59Ky8UCIGXbKgK/E4aaEX+1qJdQ6O/ZKZ8ZJiXO
                                    x82RTaehI4L1AiEAjjspgtvZuhKw0R1pQ9q8vW2tf91ms9BHVrmCm+mIU+0CIFqP
                                    A3B8EIajouhnjedvunRAh71d0kIjuZE7GUSwqMP1
                                    -----END RSA PRIVATE KEY-----";
    string publicKeySPKIPem = @"-----BEGIN PUBLIC KEY-----
                                    MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALWDYiRs0Q209/tUtdaYP5hvPs76U6U6
                                    8kJcRntz2anwtvCURhhYdAZ6GDK8jQOREzlkTGRHxlj9NVb/zNp4PdECAwEAAQ==
                                    -----END PUBLIC KEY-----";
    
    RSA privateKey = ImportFromPem(privateKeyPkcs1Pem, true);
    RSA publicKey = ImportFromPem(publicKeySPKIPem, false);
    
    byte[] ciphertext = publicKey.Encrypt(Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog"), RSAEncryptionPadding.Pkcs1);
    byte[] decrypted = privateKey.Decrypt(ciphertext, RSAEncryptionPadding.Pkcs1);
    Console.WriteLine(Encoding.UTF8.GetString(decrypted)); // The quick brown fox jumps over the lazy dog