Introduction
In .NET 5+ and .NET Core 3.1+, use RSA.Create() with ImportFromPem() to read a PEM RSA private key directly — no third-party libraries needed. For older .NET Framework, use BouncyCastle or manually strip the PEM headers and decode the Base64 content. The PEM format wraps a Base64-encoded key between -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- headers. The key may be in PKCS#1 (RSA PRIVATE KEY) or PKCS#8 (PRIVATE KEY) format, and the import method differs for each.
.NET 5+ / .NET Core 3.1+ (Recommended)
1using System.Security.Cryptography;
2
3// Read PEM file
4string pemText = File.ReadAllText("private_key.pem");
5
6// Import directly from PEM
7var rsa = RSA.Create();
8rsa.ImportFromPem(pemText.AsSpan());
9
10// Use the key
11byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
12byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
13Console.WriteLine($"Signature length: {signature.Length} bytes");
ImportFromPem() automatically detects whether the key is PKCS#1 or PKCS#8 format.
1# PKCS#1 format (RSA-specific)
2-----BEGIN RSA PRIVATE KEY-----
3MIIEowIBAAKCAQEA...
4-----END RSA PRIVATE KEY-----
5
6# PKCS#8 format (generic, may contain RSA, EC, etc.)
7-----BEGIN PRIVATE KEY-----
8MIIEvQIBADANBgkq...
9-----END PRIVATE KEY-----
10
11# PKCS#8 encrypted
12-----BEGIN ENCRYPTED PRIVATE KEY-----
13MIIFHDBOBgkqhkiG...
14-----END ENCRYPTED PRIVATE KEY-----
Import methods for each format:
1var rsa = RSA.Create();
2
3// PKCS#1: BEGIN RSA PRIVATE KEY
4rsa.ImportRSAPrivateKey(pkcs1Bytes, out _);
5
6// PKCS#8: BEGIN PRIVATE KEY
7rsa.ImportPkcs8PrivateKey(pkcs8Bytes, out _);
8
9// PKCS#8 encrypted: BEGIN ENCRYPTED PRIVATE KEY
10rsa.ImportEncryptedPkcs8PrivateKey("password", encryptedBytes, out _);
11
12// Or let ImportFromPem detect automatically (.NET 5+)
13rsa.ImportFromPem(pemText);
Manual PEM Parsing (Older .NET)
For .NET Framework 4.x where ImportFromPem is not available:
1using System;
2using System.IO;
3using System.Security.Cryptography;
4
5public static RSA ReadPemPrivateKey(string pemFilePath)
6{
7 string pemText = File.ReadAllText(pemFilePath);
8
9 // Strip PEM headers and whitespace
10 string base64;
11 if (pemText.Contains("BEGIN RSA PRIVATE KEY"))
12 {
13 base64 = pemText
14 .Replace("-----BEGIN RSA PRIVATE KEY-----", "")
15 .Replace("-----END RSA PRIVATE KEY-----", "")
16 .Replace("\n", "").Replace("\r", "").Trim();
17
18 byte[] keyBytes = Convert.FromBase64String(base64);
19 var rsa = RSA.Create();
20 rsa.ImportRSAPrivateKey(keyBytes, out _); // .NET Core 3.0+
21 return rsa;
22 }
23 else if (pemText.Contains("BEGIN PRIVATE KEY"))
24 {
25 base64 = pemText
26 .Replace("-----BEGIN PRIVATE KEY-----", "")
27 .Replace("-----END PRIVATE KEY-----", "")
28 .Replace("\n", "").Replace("\r", "").Trim();
29
30 byte[] keyBytes = Convert.FromBase64String(base64);
31 var rsa = RSA.Create();
32 rsa.ImportPkcs8PrivateKey(keyBytes, out _); // .NET Core 3.0+
33 return rsa;
34 }
35
36 throw new CryptographicException("Unsupported PEM format");
37}
Using BouncyCastle (.NET Framework)
For .NET Framework 4.x without the modern import methods:
1// Install: Install-Package BouncyCastle.Cryptography
2using Org.BouncyCastle.Crypto;
3using Org.BouncyCastle.Crypto.Parameters;
4using Org.BouncyCastle.OpenSsl;
5using Org.BouncyCastle.Security;
6using System.IO;
7using System.Security.Cryptography;
8
9public static RSA ReadPemWithBouncyCastle(string pemFilePath)
10{
11 using var reader = new StreamReader(pemFilePath);
12 var pemReader = new PemReader(reader);
13 var keyObject = pemReader.ReadObject();
14
15 RsaPrivateCrtKeyParameters privateKey;
16 if (keyObject is AsymmetricCipherKeyPair keyPair)
17 {
18 privateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;
19 }
20 else
21 {
22 privateKey = (RsaPrivateCrtKeyParameters)keyObject;
23 }
24
25 var rsaParams = DotNetUtilities.ToRSAParameters(privateKey);
26 var rsa = RSA.Create();
27 rsa.ImportParameters(rsaParams);
28 return rsa;
29}
30
31// With password-protected PEM
32public static RSA ReadEncryptedPem(string pemFilePath, string password)
33{
34 using var reader = new StreamReader(pemFilePath);
35 var pemReader = new PemReader(reader, new PasswordFinder(password));
36 var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
37 var privateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;
38
39 var rsaParams = DotNetUtilities.ToRSAParameters(privateKey);
40 var rsa = RSA.Create();
41 rsa.ImportParameters(rsaParams);
42 return rsa;
43}
44
45class PasswordFinder : IPasswordFinder
46{
47 private readonly string _password;
48 public PasswordFinder(string password) => _password = password;
49 public char[] GetPassword() => _password.ToCharArray();
50}
Practical Examples
Signing and Verifying
1// Sign data
2var rsa = RSA.Create();
3rsa.ImportFromPem(File.ReadAllText("private.pem"));
4
5byte[] data = Encoding.UTF8.GetBytes("Important message");
6byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
7
8// Verify with public key
9var rsaPublic = RSA.Create();
10rsaPublic.ImportFromPem(File.ReadAllText("public.pem"));
11bool isValid = rsaPublic.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Creating JWT Tokens
1using System.IdentityModel.Tokens.Jwt;
2using Microsoft.IdentityModel.Tokens;
3
4var rsa = RSA.Create();
5rsa.ImportFromPem(File.ReadAllText("private.pem"));
6
7var signingCredentials = new SigningCredentials(
8 new RsaSecurityKey(rsa),
9 SecurityAlgorithms.RsaSha256
10);
11
12var token = new JwtSecurityToken(
13 issuer: "myapp",
14 audience: "api",
15 expires: DateTime.UtcNow.AddHours(1),
16 signingCredentials: signingCredentials
17);
18
19string jwt = new JwtSecurityTokenHandler().WriteToken(token);
Common Pitfalls
Confusing PKCS#1 and PKCS#8 formats: ImportRSAPrivateKey expects PKCS#1 bytes (BEGIN RSA PRIVATE KEY), while ImportPkcs8PrivateKey expects PKCS#8 bytes (BEGIN PRIVATE KEY). Using the wrong method throws CryptographicException. Use ImportFromPem() (.NET 5+) to detect the format automatically.
Not stripping PEM headers before Base64 decoding: The -----BEGIN...----- and -----END...----- lines are not valid Base64. Passing the raw PEM string to Convert.FromBase64String() throws FormatException. Strip headers, footers, and all whitespace before decoding.
RSA object disposal and key lifetime: RSA.Create() returns a disposable object. In production code, dispose it with using when done. For long-lived keys (e.g., JWT signing in a web app), store the RSA instance as a singleton rather than re-reading the PEM file on every request.
File permissions on the PEM key: The private key file must be readable only by the application process. On Linux, set chmod 600 private.pem. On Windows, restrict NTFS permissions. Leaving a private key world-readable is a security vulnerability.
Using RSACryptoServiceProvider instead of RSA.Create(): The legacy RSACryptoServiceProvider does not support ImportFromPem or ImportPkcs8PrivateKey. Always use RSA.Create() which returns the modern RSACng (Windows) or RSAOpenSsl (Linux/macOS) implementation.
Summary
Use RSA.Create() + ImportFromPem() in .NET 5+ for the simplest approach — it auto-detects PKCS#1 and PKCS#8
For .NET Core 3.x, manually strip PEM headers and use ImportRSAPrivateKey() or ImportPkcs8PrivateKey()
For .NET Framework 4.x, use BouncyCastle's PemReader to parse the key and convert to RSAParameters
Always dispose RSA objects or store as singletons for long-lived signing operations
Protect private key files with appropriate filesystem permissions (600 on Linux, restricted NTFS on Windows)