implement RSA in .NET core

You should avoid using RSACryptoServiceProvider if you can. It only works on Windows (and it’s the less good RSA implementation on Windows). Stick to the RSA base class, and create new instances via RSA.Create()

Ephemeral Keys (Creation)

.NET Core

using (RSA rsa = RSA.Create())
{
    rsa.KeySize = desiredKeySizeInBits;

    // when the key next gets used it will be created at that keysize.
    DoStuffWithThePrivateKey(rsa);
}

.NET Framework

Unfortunately, the default for RSA.Create() on .NET Framework is RSACryptoServiceProvider, which doesn’t respect set_KeySize. So if you need ephemeral keys you’ll need to use different code on .NET Framework vs .NET Core:

using (RSA rsa = new RSACng())
{
    rsa.KeySize = desiredKeySizeInBits;

    DoStuffWithThePrivateKey(rsa);
}

Or, if you need to support versions earlier than 4.6 (where RSACng didn’t exist) / 4.6.2 (where most of the .NET Framework worked happily with RSACng objects instead of RSACryptoServiceProvider objects) you can continue to use the older implementation:

using (RSA rsa = new RSACryptoServiceProvider(desiredKeySizeInBits))
{
    // Since before net46 the Encrypt/Decrypt, SignData/VerifyData, SignHash/VerifyHash
    // methods were not defined at the RSA base class, you might need to keep this strongly
    // typed as RSACryptoServiceProvider.
    DoStuffWithThePrivateKey(rsa);
}

Ephemeral Keys (Import)

Even though RSACng, in general, is easier to work with than RSACryptoServiceProvider, RSACryptoServiceProvider should work fine in this context, so RSA.Create() is good on all platforms:

using (RSA rsa = RSA.Create())
{
    rsa.ImportParameters(rsaParameters);

    DoStuffWithWhateverKindOfKeyYouImported(rsa);
}

From a certificate:

.NET Core 1.0+, .NET Framework 4.6+

using (RSA rsa = cert.GetRSAPublicKey())
{
    DoStuffWithThePublicKey(rsa);
}

or

using (RSA rsa = cert.GetRSAPrivateKey())
{
    DoStuffWithThePrivateKey(rsa);
}

.NET Framework < 4.6

// Do NOT put this in a using block, since the object is shared by all callers:
RSA rsaPrivate = (RSA)cert.PrivateKey;
DoStuffWithThePrivateKey(rsaPrivate);

// Do NOT put this in a using block, since the object is shared by all callers:
RSA rsaPublicOnly = (RSA)cert.PublicKey.Key;
DoStuffWithThePublicKey(rsaPublic);

Using Named/Persisted Keys (Windows-only)

I was going to include some samples about RSACryptoServiceProvider (WinXP+/CAPI) and RSACng (Win7+/CNG) creating/opening named keys, but that’s not a very common scenario in .NET; and it certainly isn’t portable (portability to other OSes being one of the more compelling arguments for .NET Core).

Referencing things.

For .NET Core 1.0 and 1.1, you get access to the RSA base class from the System.Security.Cryptography.Algorithms package. In .NET Core 2.0 it will be included in the netstandard package reference.

If you need to do complex interop with the OS you can reference System.Security.Cryptography.Cng (Windows CNG), System.Security.Cryptography.Csp (Windows CAPI/CryptoServiceProvider), or System.Security.Cryptography.OpenSsl (Linux OpenSSL, macOS OpenSSL) and get access to the interop-enabled classes (RSACng, RSACryptoServiceProvider, RSAOpenSsl). But, really, you shouldn’t do that.

What does RSA.Create() return?

  • .NET Framework: RSACryptoServiceProvider, unless changed by CryptoConfig.
  • .NET Core (Windows): A private class which implements RSA via CNG, you can’t cast it to any more specific type.
  • .NET Core (Linux): A private class which implements RSA via OpenSSL, you can’t cast it to any more specific type.
  • .NET Core (macOS): A private class which implements RSA via OpenSSL, you can’t cast it to any more specific type. (This should be via SecureTransforms in the next release of .NET Core)

Leave a Comment