Loading RSA Keys in .NET

Scott Brady
Scott Brady
C#

System.Security.Cryptography.RSA has been the focus of many performance and usability improvements in recent major releases of .NET (and previously .NET Core). It’s a cross-platform, performant implementation of RSA signing and encryption that uses the operating system’s cryptography libraries rather than a software implementation such as Bouncy Castle.

In this article, you’ll learn the various ways of creating, parsing, and loading RSA keys in .NET, whether from a JSON Web Key (JWK) or a certificate generated by OpenSSL. This article serves as a reference for loading in RSA keys for my various C# articles on JWTs and cryptography. Code samples work for .NET 6 onwards.

.NET’s RSA object

The RSA object lives in System.Security.Cryptography namespace. It inherits AsymmetricAlgorithm and uses a similar pattern to other implementations such as ECDSA.

Your primary usage of the RSA object will likely be the SignData and VerifyData methods, which get called if you pass an RSA object to a SecurityKey implementation or SignedXml.

You can also use RSA for asymmetric encryption using the Encrypt and Decrypt methods. Just don’t forget only to use OAEP as your padding scheme for encryption.

RSA implements IDisposable and can call into unmanaged code, so make sure you use a using statement.

Creating a new in-memory RSA key in .NET

The simplest way to get a new RSA key is to have .NET generate one for you by calling the object’s Create factory method.

using var key = RSA.Create(keySizeInBits: 3072);

This method has a few overloads, but I recommend passing in either the desired key length, like the example above, or an instance of valid RsaParameters like you’ll see shortly.

While 2048 is the minimum key length required by specifications such as JOSE (JWTs), it is recommended that you use 3072, which gives you 128-bit security.

This approach is useful when writing tests or if you want your system to automatically generate and distribute keys. For example, it might generate them in memory and then store the key parameters in the database as a JSON Web Key (JWK). The public key can then be distributed to other parties so that they can validate signatures or encrypt data that only your system can read.

Loading RSA keys from a JSON Web Key (JWK) in .NET

When using distributed systems and security protocols such as OAuth and OpenID Connect, you will likely receive an RSA public key from another party, serialized as a JSON Web Key (JWK).

A JWK for an RSA public key looks like this:

{
  "kty": "RSA",
  "alg": "RS256",
  "use": "sig",
  "kid": "334703f667abaaf9826239ce88c52856",
  "n": "pgapT6UMCwD4x5df2XdgiaJTN4hFtTTjHruRwpqtdCdJijo3fYKtmbuT-xtqKvbaNtH_hkRGD_N8MULSXYTY8HZNfBgZkIvMyRz9gfu_Cu_TtxeeYZsnjnyK1IIXl1pfNOz9co7vq5PISgPW-6Mfsv1sUmFjNhOaA4hoH7gDTyEluo8lj-zswhVt9IFD-zhvlOYaN-4rUbVVy8-kGEhtDAC8kgv-w6XYUQ3a7tQFD3qjQkzxnMIE7zG-h21_CjoqTFQZfu6q1C9W1MIkGFzS9UZwoijPAwpk6OHwPruTg5hfFipktRf5E4DV3LKRF9kSg0ZM_YQF95oiQdDbdK-YS3qXErJqtO-Entep1pqluZS1BQgSqiIJ48-_5b46l_GZkWH2xvls46MlX5gxhkaES8DaWfKDmLaTYup3R-Y_EI3i4vDBMKoMBdS1JKRdoZxO9MY70qMLdQ0QcmgccC6i_PUCpbPvhQHp8lVgQ0T4_fopVCNIWJyaKj-kPN-jsNgh",
  "e": "AQAB"
}

A JWK containing an RSA key will always have a kty (key type) of “RSA” and an alg (algorithm) value starting with “RS” when using the RSASSA-PKCS1-v1.5 scheme, or “PS” when using the RSASSA-PSS scheme. The public key itself is contained in the n (modulus) and e (exponent) parameters.

A JWK can also contain an RSA private key. This has many more parameters, and while most parameters are officially “optimizations”, they are all required by .NET’s JWK libraries.

{
  "kty": "RSA",
  "alg": "RS256",
  "use": "sig",
  "kid": "334703f667abaaf9826239ce88c52856",
  "n": "pgapT6UMCwD4x5df2XdgiaJTN4hFtTTjHruRwpqtdCdJijo3fYKtmbuT-xtqKvbaNtH_hkRGD_N8MULSXYTY8HZNfBgZkIvMyRz9gfu_Cu_TtxeeYZsnjnyK1IIXl1pfNOz9co7vq5PISgPW-6Mfsv1sUmFjNhOaA4hoH7gDTyEluo8lj-zswhVt9IFD-zhvlOYaN-4rUbVVy8-kGEhtDAC8kgv-w6XYUQ3a7tQFD3qjQkzxnMIE7zG-h21_CjoqTFQZfu6q1C9W1MIkGFzS9UZwoijPAwpk6OHwPruTg5hfFipktRf5E4DV3LKRF9kSg0ZM_YQF95oiQdDbdK-YS3qXErJqtO-Entep1pqluZS1BQgSqiIJ48-_5b46l_GZkWH2xvls46MlX5gxhkaES8DaWfKDmLaTYup3R-Y_EI3i4vDBMKoMBdS1JKRdoZxO9MY70qMLdQ0QcmgccC6i_PUCpbPvhQHp8lVgQ0T4_fopVCNIWJyaKj-kPN-jsNgh",
  "e": "AQAB",
  "d": "DFWG2wafLisYkFVf1stOVfND_OZoDVX8QIJ9SeiNVcl8ZmM8T5v3cyoTDI7lFIOJwxRXSB2G2fUEBzHYaH-v0S9swrkUnx8vq8o2UCOIdhLKlvnPd59b8TMx6icvAvzBAXDQxGO3jPbZ5JvD6yZbesPmeflHslMC-Fu4JpEYV9bV03YCe56FmUaXyFgZGY8ABR8SGBEe0T9oPA-1OGObDE76s1vmRZkqyJy_pMoGBTgebFTEh1mHndCDf9Kj4zneX229WLgFIMAuUF6NsJIcvdoFQCM63saWQpI3JfbItYRqYEhjC5y7W-2y7DQGTNafMR8X38-GebhJvHCm4qiXzenbdQIHeEE85BWzHw9M2YlSZ8vMl3zQQMTh1vMZg07eYqwzr1zMYxI8HJAQKB8NR2VDe0XQS7vWouV4KTGvw4BLY0wNAy2vOKbyCEvyiBpYTrGYkpgjspJHbxJMGscq0vm90CsALvWnd4BpKoFBPiCBnshYddZAur6DKLBlNW6R",
  "dp": "u5ZWFN_fbfkuUR7vAAAZudu6Wvn68HNgrHNgBilS4ohkUYdf8Mevt460kCmaZUxwjFByVOhULZhLE6zgtmr_Fo7tD-4pYR-ajetXWKYq5m1Kp7o4_BEqtG7XhUuzKWRpgnTQKB26gJSPaKK8diIq7ONb1N_6wwElC3-4H8fOLMlINtebuw26xX4LMSNFfvHlpf-7biHeTKi-pEPcWlSLSpYj1i1larIKTflY8B_VQvkdyhsG9m0R-nkahryKsANx",
  "dq": "uSEU5laDj40xRSYu5s5uExtHIaP02YQMDlyhcYEj3W92xF8cUPvJ1DvpNlRDlDHxv8wN74wXyao7IpAgL7vBPTY1V9pbmPW_tdP3SISjkHp9UG0tEg8PQMQhAGh8bq7SFaTc2tRdiDJHKTYvFYFPIJuoLkmKA3K3THHyncsOk9fqOlaSTcQjmI27Z0f4wbt16xvIH_xAitwZ0j17HKv6bF62SNkDUjvVPd9t-3dVTvM4qq9yrtPrESpx206C16sh",
  "p": "1dggd6aIYBRcND9f-V64zlspZdKWpSlIqZYX21ltw7i_agFp5pS2fwtkJErSKA20myUohNop5uMxQiTWJeb2nHe_OzGsjG68By_EmP4DEPdyPY-8RZ45wMHYdVvCbfNHCYxuUATbQXH8jMIiuBQq7BZyMUQ-iF3eTY5SzgdY3-ROny7mqCHMDq44XuT26r0p9IkM4Jg9RBlg8fdvbnIuSfqPdSUOPa4I2j8RFEOsVSs0teXNuUmOO-ovXGBwe-P5",
  "q": "xsFWgVB6Av0zufrRGhLXfs5oBjg5Bg84qJbnuBSfOGTeHT8PLnu-G9YvwBxsq0KaoyRxLYS8cEPsqXj5eVk6qlzGeJwFHQr07UcpzgcQAgE7aWgLIp-q_BJ1rWg99msggvCHAI8F-WPS9SK1UT3vZqgWLzYiLKQJDgu0cf3fBCMVswHQenuiyRFyEqJYtlN4STZY2uVQQ3zXP0E53YHZjbwj7gO1O0uaDM1OVWfsTJiMXNUHW13Lt7CzlNgeWs9p",
  "qi": "gTOcxTsCHkDuDxsePtg0cG40TKBwc72DDsGBJK1OBVVOLT7llQvMwWtRmiXtrkJLNtjSpvKN7taDy3gZQPwrJnUCL5w10orpNkU5-8_nK2tBp4kL7okuGMzr185Vh55NTCtLW02itqBm-_oMjG7CQ_92vQ7xH5BP56whX7naUGrmIqmDbf7cYwLAGf8GywRstrorzug9RHvQMYsiUqWfTY57rmxEH3ZIw-bJ5a2Pkmzb31qCXcX9uc-H0uLXCFLP"
}

Let’s see how to parse the JWKs in .NET.

Manually parsing RsaParameters in .NET

You can use a JWK to manually configure an RsaParameters object. To load the public key, you only need to set the Modulus and Exponent properties; however, to load the private key, you need to set every property.

const string json = "{\"alg\":\"RS256\",\"kty\":\"RSA\",\"use\":\"sig\",\"kid\":\"334703f667abaaf9826239ce88c52856\",\"n\":\"pgapT6UMCwD4x5df2XdgiaJTN4hFtTTjHruRwpqtdCdJijo3fYKtmbuT-xtqKvbaNtH_hkRGD_N8MULSXYTY8HZNfBgZkIvMyRz9gfu_Cu_TtxeeYZsnjnyK1IIXl1pfNOz9co7vq5PISgPW-6Mfsv1sUmFjNhOaA4hoH7gDTyEluo8lj-zswhVt9IFD-zhvlOYaN-4rUbVVy8-kGEhtDAC8kgv-w6XYUQ3a7tQFD3qjQkzxnMIE7zG-h21_CjoqTFQZfu6q1C9W1MIkGFzS9UZwoijPAwpk6OHwPruTg5hfFipktRf5E4DV3LKRF9kSg0ZM_YQF95oiQdDbdK-YS3qXErJqtO-Entep1pqluZS1BQgSqiIJ48-_5b46l_GZkWH2xvls46MlX5gxhkaES8DaWfKDmLaTYup3R-Y_EI3i4vDBMKoMBdS1JKRdoZxO9MY70qMLdQ0QcmgccC6i_PUCpbPvhQHp8lVgQ0T4_fopVCNIWJyaKj-kPN-jsNgh\",\"e\":\"AQAB\",\"d\":\"DFWG2wafLisYkFVf1stOVfND_OZoDVX8QIJ9SeiNVcl8ZmM8T5v3cyoTDI7lFIOJwxRXSB2G2fUEBzHYaH-v0S9swrkUnx8vq8o2UCOIdhLKlvnPd59b8TMx6icvAvzBAXDQxGO3jPbZ5JvD6yZbesPmeflHslMC-Fu4JpEYV9bV03YCe56FmUaXyFgZGY8ABR8SGBEe0T9oPA-1OGObDE76s1vmRZkqyJy_pMoGBTgebFTEh1mHndCDf9Kj4zneX229WLgFIMAuUF6NsJIcvdoFQCM63saWQpI3JfbItYRqYEhjC5y7W-2y7DQGTNafMR8X38-GebhJvHCm4qiXzenbdQIHeEE85BWzHw9M2YlSZ8vMl3zQQMTh1vMZg07eYqwzr1zMYxI8HJAQKB8NR2VDe0XQS7vWouV4KTGvw4BLY0wNAy2vOKbyCEvyiBpYTrGYkpgjspJHbxJMGscq0vm90CsALvWnd4BpKoFBPiCBnshYddZAur6DKLBlNW6R\",\"dp\":\"u5ZWFN_fbfkuUR7vAAAZudu6Wvn68HNgrHNgBilS4ohkUYdf8Mevt460kCmaZUxwjFByVOhULZhLE6zgtmr_Fo7tD-4pYR-ajetXWKYq5m1Kp7o4_BEqtG7XhUuzKWRpgnTQKB26gJSPaKK8diIq7ONb1N_6wwElC3-4H8fOLMlINtebuw26xX4LMSNFfvHlpf-7biHeTKi-pEPcWlSLSpYj1i1larIKTflY8B_VQvkdyhsG9m0R-nkahryKsANx\",\"dq\":\"uSEU5laDj40xRSYu5s5uExtHIaP02YQMDlyhcYEj3W92xF8cUPvJ1DvpNlRDlDHxv8wN74wXyao7IpAgL7vBPTY1V9pbmPW_tdP3SISjkHp9UG0tEg8PQMQhAGh8bq7SFaTc2tRdiDJHKTYvFYFPIJuoLkmKA3K3THHyncsOk9fqOlaSTcQjmI27Z0f4wbt16xvIH_xAitwZ0j17HKv6bF62SNkDUjvVPd9t-3dVTvM4qq9yrtPrESpx206C16sh\",\"p\":\"1dggd6aIYBRcND9f-V64zlspZdKWpSlIqZYX21ltw7i_agFp5pS2fwtkJErSKA20myUohNop5uMxQiTWJeb2nHe_OzGsjG68By_EmP4DEPdyPY-8RZ45wMHYdVvCbfNHCYxuUATbQXH8jMIiuBQq7BZyMUQ-iF3eTY5SzgdY3-ROny7mqCHMDq44XuT26r0p9IkM4Jg9RBlg8fdvbnIuSfqPdSUOPa4I2j8RFEOsVSs0teXNuUmOO-ovXGBwe-P5\",\"q\":\"xsFWgVB6Av0zufrRGhLXfs5oBjg5Bg84qJbnuBSfOGTeHT8PLnu-G9YvwBxsq0KaoyRxLYS8cEPsqXj5eVk6qlzGeJwFHQr07UcpzgcQAgE7aWgLIp-q_BJ1rWg99msggvCHAI8F-WPS9SK1UT3vZqgWLzYiLKQJDgu0cf3fBCMVswHQenuiyRFyEqJYtlN4STZY2uVQQ3zXP0E53YHZjbwj7gO1O0uaDM1OVWfsTJiMXNUHW13Lt7CzlNgeWs9p\",\"qi\":\"gTOcxTsCHkDuDxsePtg0cG40TKBwc72DDsGBJK1OBVVOLT7llQvMwWtRmiXtrkJLNtjSpvKN7taDy3gZQPwrJnUCL5w10orpNkU5-8_nK2tBp4kL7okuGMzr185Vh55NTCtLW02itqBm-_oMjG7CQ_92vQ7xH5BP56whX7naUGrmIqmDbf7cYwLAGf8GywRstrorzug9RHvQMYsiUqWfTY57rmxEH3ZIw-bJ5a2Pkmzb31qCXcX9uc-H0uLXCFLP\"}";
var parsedKey = JsonNode.Parse(json);

var rsaParameters = new RSAParameters
{
    // PUBLIC KEY PARAMETERS
    // n parameter - public modulus
    Modulus = Base64UrlEncoder.DecodeBytes(parsedKey["n"].ToString()),
    // e parameter - public exponent
    Exponent = Base64UrlEncoder.DecodeBytes(parsedKey["e"].ToString()),
    
    // PRIVATE KEY PARAMETERS
    // d parameter - the private exponent value for the RSA key 
    D = Base64UrlEncoder.DecodeBytes(parsedKey["d"].ToString()),
    // dp parameter - CRT exponent of the first factor
    DP = Base64UrlEncoder.DecodeBytes(parsedKey["dp"].ToString()),
    // dq parameter - CRT exponent of the second factor
    DQ = Base64UrlEncoder.DecodeBytes(parsedKey["dq"].ToString()),
    // p parameter - first prime factor
    P = Base64UrlEncoder.DecodeBytes(parsedKey["p"].ToString()),
    // q parameter - second prime factor
    Q = Base64UrlEncoder.DecodeBytes(parsedKey["q"].ToString()),
    // qi parameter - CRT coefficient of the second factor
    InverseQ = Base64UrlEncoder.DecodeBytes(parsedKey["qi"].ToString()),
};

using var key = RSA.Create(rsaParameters);

JSON Web Keys base64url encode key parameters. In this case, I’m using the base64url implementation found in Microsoft.IdentityModel.Tokens. An alternative is available in Microsoft.AspNetCore.Authentication.

You can also use the ImportParameters method on RSA to load the parameters into an existing key.

Loading an RSA JWK using Microsoft.IdentityModel

Rather than manually parsing the JWK yourself, you could use the JsonWebKey class found in Microsoft.IdentityModel.Tokens.

const string json = "{\"alg\":\"RS256\",\"kty\":\"RSA\",\"use\":\"sig\",\"kid\":\"334703f667abaaf9826239ce88c52856\",\"n\":\"pgapT6UMCwD4x5df2XdgiaJTN4hFtTTjHruRwpqtdCdJijo3fYKtmbuT-xtqKvbaNtH_hkRGD_N8MULSXYTY8HZNfBgZkIvMyRz9gfu_Cu_TtxeeYZsnjnyK1IIXl1pfNOz9co7vq5PISgPW-6Mfsv1sUmFjNhOaA4hoH7gDTyEluo8lj-zswhVt9IFD-zhvlOYaN-4rUbVVy8-kGEhtDAC8kgv-w6XYUQ3a7tQFD3qjQkzxnMIE7zG-h21_CjoqTFQZfu6q1C9W1MIkGFzS9UZwoijPAwpk6OHwPruTg5hfFipktRf5E4DV3LKRF9kSg0ZM_YQF95oiQdDbdK-YS3qXErJqtO-Entep1pqluZS1BQgSqiIJ48-_5b46l_GZkWH2xvls46MlX5gxhkaES8DaWfKDmLaTYup3R-Y_EI3i4vDBMKoMBdS1JKRdoZxO9MY70qMLdQ0QcmgccC6i_PUCpbPvhQHp8lVgQ0T4_fopVCNIWJyaKj-kPN-jsNgh\",\"e\":\"AQAB\",\"d\":\"DFWG2wafLisYkFVf1stOVfND_OZoDVX8QIJ9SeiNVcl8ZmM8T5v3cyoTDI7lFIOJwxRXSB2G2fUEBzHYaH-v0S9swrkUnx8vq8o2UCOIdhLKlvnPd59b8TMx6icvAvzBAXDQxGO3jPbZ5JvD6yZbesPmeflHslMC-Fu4JpEYV9bV03YCe56FmUaXyFgZGY8ABR8SGBEe0T9oPA-1OGObDE76s1vmRZkqyJy_pMoGBTgebFTEh1mHndCDf9Kj4zneX229WLgFIMAuUF6NsJIcvdoFQCM63saWQpI3JfbItYRqYEhjC5y7W-2y7DQGTNafMR8X38-GebhJvHCm4qiXzenbdQIHeEE85BWzHw9M2YlSZ8vMl3zQQMTh1vMZg07eYqwzr1zMYxI8HJAQKB8NR2VDe0XQS7vWouV4KTGvw4BLY0wNAy2vOKbyCEvyiBpYTrGYkpgjspJHbxJMGscq0vm90CsALvWnd4BpKoFBPiCBnshYddZAur6DKLBlNW6R\",\"dp\":\"u5ZWFN_fbfkuUR7vAAAZudu6Wvn68HNgrHNgBilS4ohkUYdf8Mevt460kCmaZUxwjFByVOhULZhLE6zgtmr_Fo7tD-4pYR-ajetXWKYq5m1Kp7o4_BEqtG7XhUuzKWRpgnTQKB26gJSPaKK8diIq7ONb1N_6wwElC3-4H8fOLMlINtebuw26xX4LMSNFfvHlpf-7biHeTKi-pEPcWlSLSpYj1i1larIKTflY8B_VQvkdyhsG9m0R-nkahryKsANx\",\"dq\":\"uSEU5laDj40xRSYu5s5uExtHIaP02YQMDlyhcYEj3W92xF8cUPvJ1DvpNlRDlDHxv8wN74wXyao7IpAgL7vBPTY1V9pbmPW_tdP3SISjkHp9UG0tEg8PQMQhAGh8bq7SFaTc2tRdiDJHKTYvFYFPIJuoLkmKA3K3THHyncsOk9fqOlaSTcQjmI27Z0f4wbt16xvIH_xAitwZ0j17HKv6bF62SNkDUjvVPd9t-3dVTvM4qq9yrtPrESpx206C16sh\",\"p\":\"1dggd6aIYBRcND9f-V64zlspZdKWpSlIqZYX21ltw7i_agFp5pS2fwtkJErSKA20myUohNop5uMxQiTWJeb2nHe_OzGsjG68By_EmP4DEPdyPY-8RZ45wMHYdVvCbfNHCYxuUATbQXH8jMIiuBQq7BZyMUQ-iF3eTY5SzgdY3-ROny7mqCHMDq44XuT26r0p9IkM4Jg9RBlg8fdvbnIuSfqPdSUOPa4I2j8RFEOsVSs0teXNuUmOO-ovXGBwe-P5\",\"q\":\"xsFWgVB6Av0zufrRGhLXfs5oBjg5Bg84qJbnuBSfOGTeHT8PLnu-G9YvwBxsq0KaoyRxLYS8cEPsqXj5eVk6qlzGeJwFHQr07UcpzgcQAgE7aWgLIp-q_BJ1rWg99msggvCHAI8F-WPS9SK1UT3vZqgWLzYiLKQJDgu0cf3fBCMVswHQenuiyRFyEqJYtlN4STZY2uVQQ3zXP0E53YHZjbwj7gO1O0uaDM1OVWfsTJiMXNUHW13Lt7CzlNgeWs9p\",\"qi\":\"gTOcxTsCHkDuDxsePtg0cG40TKBwc72DDsGBJK1OBVVOLT7llQvMwWtRmiXtrkJLNtjSpvKN7taDy3gZQPwrJnUCL5w10orpNkU5-8_nK2tBp4kL7okuGMzr185Vh55NTCtLW02itqBm-_oMjG7CQ_92vQ7xH5BP56whX7naUGrmIqmDbf7cYwLAGf8GywRstrorzug9RHvQMYsiUqWfTY57rmxEH3ZIw-bJ5a2Pkmzb31qCXcX9uc-H0uLXCFLP\"}";

var jsonWebKey = new JsonWebKey(json);
// var rsaParameters = jsonWebKey.CreateRsaParameters(); // is internal 😭 

var rsaParameters = new RSAParameters
{
    // PUBLIC KEY PARAMETERS
    // n parameter - public modulus
    Modulus = Base64UrlEncoder.DecodeBytes(jsonWebKey.N),
    // e parameter - public exponent
    Exponent = Base64UrlEncoder.DecodeBytes(jsonWebKey.E),
    
    // PRIVATE KEY PARAMETERS (optional)
    // d parameter - the private exponent value for the RSA key 
    D = Base64UrlEncoder.DecodeBytes(jsonWebKey.D),
    // dp parameter - CRT exponent of the first factor
    DP = Base64UrlEncoder.DecodeBytes(jsonWebKey.DP),
    // dq parameter - CRT exponent of the second factor
    DQ = Base64UrlEncoder.DecodeBytes(jsonWebKey.DQ),
    // p parameter - first prime factor
    P = Base64UrlEncoder.DecodeBytes(jsonWebKey.P),
    // q parameter - second prime factor
    Q = Base64UrlEncoder.DecodeBytes(jsonWebKey.Q),
    // qi parameter - CRT coefficient of the second factor
    InverseQ = Base64UrlEncoder.DecodeBytes(jsonWebKey.QI)
};

using var key = RSA.Create(rsaParameters);

JsonWebKey implements SecurityKey, meaning you can use it with JsonWebTokenHandler.

var result = new JsonWebTokenHandler().ValidateToken(
    jwt, 
    new TokenValidationParameters
    {
        ValidIssuer = "me",
        ValidAudience = "you",
        IssuerSigningKey = jsonWebKey
    });

Unfortunately, the methods it uses to parse the RSA or RsaSecurityKey objects are internal. Still, its parsing and validation of the JWK can be useful, saving you from loading properties yourself and validating private key presence with the HasPrivateKey method.

Loading an RSA key from an OpenSSL PEM file in .NET

To load an RSA key from a PEM file, you can use the ImportFromPem method introduced in .NET 5. This method handles the PKCS #1 and PKCS #8 formats generated by OpenSSL.

using var key = RSA.Create();
key.ImportFromPem(File.ReadAllText("key-pkcs1.pem"));

This method saves you from parsing and validating PEM headers and footers or handling any decoding, which you had to do in older versions of .NET.

Loading an RSA key from an X509 certificate in .NET

To load an RSA key from an X509Certificate2 object, you can use the GetRSAPrivateKey or GetRSAPublicKey methods which return an instance of RSA. You can use the HasPrivateKey method to decide which method to use.

var cert = new X509Certificate2("cert.pfx");
using var key = cert.GetRSAPrivateKey() ?? cert.GetRSAPublicKey();

GetRSAPrivateKey will return null if the certificate does not contain a copy of the private key. While you could use the PrivateKey property, this is now marked as obsolete as of .NET 6.

Source code

You can find sample code for the above approaches in my GitHub samples repository. These samples are tested against .NET 6 onwards and run on both Windows and Linux.

To learn more about RSA usage in .NET, check out some of my other articles: