You don't.
.NET doesn't support it.
You see, the PEM-encoding does support multiple keys types (e.g. ECDSA).
So if somebody provides you with a way to read a .NET RSA key from a PEM-file, you maybe able to read that RSA-key (if the PEM also has D, P, Q, DP DQ, InverseQ), but that's a long way from reading PEM.
Also the .NET version of RSA only implements RSA based on (Modulus,Exponent and D, P, Q, DP, DQ, InverseQ). A real PEM-encoded RSA key does not need to support parameters D, P, Q, DP, DQ, InverseQ. It can, but it doesn't have to. All RSA really needs is Modulus and Exponent. And a PEM-encoded RSA key containing only modulus and Exponent is a perferctly valid PEM-key, especially when you need interop with Python, which does such things.
However, you can read a PEM-encoded private and public key with BouncyCastle.
See below.
To get these cryptographic keys (of any kind) generically to .NET without adding a dependency on BouncyCastle, your best bet is to read the PEM-files in BouncyCastle, create a PFX-file (which contains private and public key), then read that PFX-file with System.Security.Cryptography.X509Certificates.X509Certificate2. From that certificate, you can then get the private and public key already decoded, assuming they use a cryptographic algorithm of a kind that .NET does support.
namespace SslCertificateGenerator
{
// https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414
public class KeyImportExport
{
// KeyImportExport.GetPemKeyPair
public static PrivatePublicPemKeyPair GetPemKeyPair(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair)
{
PrivatePublicPemKeyPair result = new PrivatePublicPemKeyPair();
// id_rsa
using (System.IO.TextWriter textWriter = new System.IO.StringWriter())
{
Org.BouncyCastle.OpenSsl.PemWriter pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
result.PrivateKey = textWriter.ToString();
} // End Using textWriter
// id_rsa.pub
using (System.IO.TextWriter textWriter = new System.IO.StringWriter())
{
Org.BouncyCastle.OpenSsl.PemWriter pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Public);
pemWriter.Writer.Flush();
result.PublicKey = textWriter.ToString();
} // End Using textWriter
// // This writes the same as private key, not both
//using (System.IO.TextWriter textWriter = new System.IO.StringWriter())
//{
// Org.BouncyCastle.OpenSsl.PemWriter pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(textWriter);
// pemWriter.WriteObject(keyPair);
// pemWriter.Writer.Flush();
// bothKeys = textWriter.ToString();
//} // End Using textWriter
return result;
} // End Sub GetPemKeyPair
// KeyImportExport.ReadPublicKey
public static Org.BouncyCastle.Crypto.AsymmetricKeyParameter ReadPublicKey(string publicKey)
{
Org.BouncyCastle.Crypto.AsymmetricKeyParameter keyParameter = null;
using (System.IO.TextReader reader = new System.IO.StringReader(publicKey))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader =
new Org.BouncyCastle.OpenSsl.PemReader(reader);
object obj = pemReader.ReadObject();
if ((obj is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair))
throw new System.ArgumentException("The given publicKey is actually a private key.", "publicKey");
if (!(obj is Org.BouncyCastle.Crypto.AsymmetricKeyParameter))
throw new System.ArgumentException("The given publicKey is not a valid assymetric key.", "publicKey");
keyParameter = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)obj;
}
return keyParameter;
} // End Function ReadPublicKey
public static Org.BouncyCastle.Crypto.AsymmetricKeyParameter ReadPrivateKey(string privateKey)
{
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair = null;
using (System.IO.TextReader reader = new System.IO.StringReader(privateKey))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader =
new Org.BouncyCastle.OpenSsl.PemReader(reader);
object obj = pemReader.ReadObject();
if (obj is Org.BouncyCastle.Crypto.AsymmetricKeyParameter)
throw new System.ArgumentException("The given privateKey is a public key, not a privateKey...", "privateKey");
if (!(obj is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair))
throw new System.ArgumentException("The given privateKey is not a valid assymetric key.", "privateKey");
keyPair = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)obj;
} // End using reader
// Org.BouncyCastle.Crypto.AsymmetricKeyParameter priv = keyPair.Private;
// Org.BouncyCastle.Crypto.AsymmetricKeyParameter pub = keyPair.Public;
// Note:
// cipher.Init(false, key);
// !!!
return keyPair.Private;
} // End Function ReadPrivateKey
public static Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair ReadKeyPair(string privateKey)
{
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair = null;
using (System.IO.TextReader reader = new System.IO.StringReader(privateKey))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader =
new Org.BouncyCastle.OpenSsl.PemReader(reader);
object obj = pemReader.ReadObject();
if (obj is Org.BouncyCastle.Crypto.AsymmetricKeyParameter)
throw new System.ArgumentException("The given privateKey is a public key, not a privateKey...", "privateKey");
if (!(obj is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair))
throw new System.ArgumentException("The given privateKey is not a valid assymetric key.", "privateKey");
keyPair = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)obj;
}
// Org.BouncyCastle.Crypto.AsymmetricKeyParameter priv = keyPair.Private;
// Org.BouncyCastle.Crypto.AsymmetricKeyParameter pub = keyPair.Public;
// Note:
// cipher.Init(false, key);
// !!!
return keyPair;
} // End Function ReadPrivateKey
public static Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair ReadKeyPairFromFile(string fileName)
{
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair KeyPair = null;
// Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
using (System.IO.FileStream fs = System.IO.File.OpenRead(fileName))
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(fs))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr);
KeyPair = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)pemReader.ReadObject();
// System.Security.Cryptography.RSAParameters rsa = Org.BouncyCastle.Security.
// DotNetUtilities.ToRSAParameters((Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)KeyPair.Private);
} // End Using sr
} // End Using fs
return KeyPair;
} // End Function ImportKeyPair
//public static void ReadPrivateKeyFile(string privateKeyFileName)
//{
// Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters key = null;
// using (System.IO.StreamReader streamReader = System.IO.File.OpenText(privateKeyFileName))
// {
// Org.BouncyCastle.OpenSsl.PemReader pemReader =
// new Org.BouncyCastle.OpenSsl.PemReader(streamReader);
// key = (Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters) pemReader.ReadObject();
// } // End Using streamReader
// // Note:
// // cipher.Init(false, key);
// // !!!
//} // End Function ReadPrivateKey
public Org.BouncyCastle.Crypto.AsymmetricKeyParameter ReadPublicKeyFile(string pemFilename)
{
Org.BouncyCastle.Crypto.AsymmetricKeyParameter keyParameter = null;
using (System.IO.StreamReader streamReader = System.IO.File.OpenText(pemFilename))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(streamReader);
keyParameter = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)pemReader.ReadObject();
} // End Using fileStream
return keyParameter;
} // End Function ReadPublicKey
public static void ExportKeyPair(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair)
{
string privateKey = null;
using (System.IO.TextWriter textWriter = new System.IO.StringWriter())
{
Org.BouncyCastle.OpenSsl.PemWriter pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
privateKey = textWriter.ToString();
} // End Using textWriter
System.Console.WriteLine(privateKey);
} // End Sub ExportKeyPair
// https://mcmap.net/q/409471/-generating-keypair-using-bouncy-castle
// https://mcmap.net/q/146042/-converting-a-public-key-in-subjectpublickeyinfo-format-to-rsapublickey-format-java
// https://mcmap.net/q/409472/-get-der-encoded-public-key
// http://www.programcreek.com/java-api-examples/index.php?api=org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory
public static void CerKeyInfo(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair)
{
Org.BouncyCastle.Asn1.Pkcs.PrivateKeyInfo pkInfo = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
string privateKey = System.Convert.ToBase64String(pkInfo.GetDerEncoded());
// and following for public:
Org.BouncyCastle.Asn1.X509.SubjectPublicKeyInfo info = Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
string publicKey = System.Convert.ToBase64String(info.GetDerEncoded());
System.Console.WriteLine(privateKey);
System.Console.WriteLine(publicKey);
} // End Sub CerKeyInfo
} // End Class KeyImportExport
} // End Namespace RedmineMailService.CertSSL
Oh, and here's how to do the things with PFX:
namespace SelfSignedCertificateGenerator
{
public class PfxData
{
public Org.BouncyCastle.X509.X509Certificate Certificate;
public Org.BouncyCastle.Crypto.AsymmetricKeyParameter PrivateKey;
}
public class PfxFile
{
// System.Security.Cryptography.X509Certificates.X509Certificate2.Import (string fileName);
// https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.import?view=netframework-4.7.2
// https://gist.github.com/yutopio/a217a4af63cf6bcf0a530c14c074cf8f
// https://gist.githubusercontent.com/yutopio/a217a4af63cf6bcf0a530c14c074cf8f/raw/42b2f8cb27f6d22b7e22d65da5bbd0f1ce9b2fff/cert.cs
// https://mcmap.net/q/409473/-store-pkcs-12-container-pfx-with-bouncycastle
// https://github.com/Worlaf/RSADemo/blob/328692e28e48db92340d55563480c8724d916384/RSADemo_WinForms/frmRsaDemo.cs
public static void Create(
string fileName
, Org.BouncyCastle.X509.X509Certificate certificate
, Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey
, string password = "")
{
// create certificate entry
Org.BouncyCastle.Pkcs.X509CertificateEntry certEntry =
new Org.BouncyCastle.Pkcs.X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
if (!friendlyName.Contains("obelix", System.StringComparison.InvariantCultureIgnoreCase))
friendlyName = "Skynet Certification Authority";
else
friendlyName = "Coopérative Ménhir Obelix Gmbh & Co. KGaA";
// get bytes of private key.
Org.BouncyCastle.Asn1.Pkcs.PrivateKeyInfo keyInfo = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
//byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();
Org.BouncyCastle.Pkcs.Pkcs12StoreBuilder builder = new Org.BouncyCastle.Pkcs.Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
Org.BouncyCastle.Pkcs.Pkcs12Store store = builder.Build();
store.SetCertificateEntry(friendlyName, certEntry);
// create store entry
store.SetKeyEntry(
//keyFriendlyName
friendlyName
, new Org.BouncyCastle.Pkcs.AsymmetricKeyEntry(privateKey)
, new Org.BouncyCastle.Pkcs.X509CertificateEntry[] { certEntry }
);
byte[] pfxBytes = null;
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
// Cert is contained in store
// null: no password, "": an empty passwords
// note: Linux needs empty password on null...
store.Save(stream, password == null ? "".ToCharArray() : password.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom());
// stream.Position = 0;
pfxBytes = stream.ToArray();
} // End Using stream
#if WITH_MS_PFX
WithMsPfx(pfxBytes, fileName, password);
#else
byte[] result = Org.BouncyCastle.Pkcs.Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
// this.StoreCertificate(System.Convert.ToBase64String(result));
using (System.IO.BinaryWriter writer = new System.IO.BinaryWriter(System.IO.File.Open(fileName, System.IO.FileMode.Create)))
{
writer.Write(result);
} // End Using writer
#endif
} // End Sub Create
private static void WithMsPfx(byte[] pfxBytes, string fileName, string password)
{
System.Security.Cryptography.X509Certificates.X509Certificate2 convertedCertificate =
new System.Security.Cryptography.X509Certificates.X509Certificate2(pfxBytes,
"", // PW
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
byte[] bytes = convertedCertificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pfx, password);
System.IO.File.WriteAllBytes(fileName, bytes);
} // End Sub WithMsPfx
public static PfxData Read(string pfxFilePath, string password = "")
{
Org.BouncyCastle.Pkcs.Pkcs12Store store = null;
using (System.IO.Stream pfxStream = System.IO.File.OpenRead(pfxFilePath))
{
store = new Org.BouncyCastle.Pkcs.Pkcs12Store(pfxStream, password.ToCharArray());
}
// System.Console.WriteLine(store);
foreach (string alias in store.Aliases)
{
Org.BouncyCastle.Pkcs.X509CertificateEntry certEntry = store.GetCertificate(alias);
Org.BouncyCastle.X509.X509Certificate cert = certEntry.Certificate;
// Org.BouncyCastle.Crypto.AsymmetricKeyParameter publicKey = cert.GetPublicKey();
// System.Console.WriteLine(publicKey);
// https://7thzero.com/blog/bouncy-castle-convert-a-bouncycastle-asymmetrickeyentry-to-a-.ne
if (store.IsKeyEntry(alias))
{
Org.BouncyCastle.Pkcs.AsymmetricKeyEntry keyEntry = store.GetKey(alias);
Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey = keyEntry.Key;
if (privateKey.IsPrivate)
return new PfxData()
{
Certificate = cert,
PrivateKey = privateKey
};
} // End if (store.IsKeyEntry((string)alias))
} // Next alias
return null;
} // End Sub Read
public static System.Security.Cryptography.X509Certificates.X509Certificate2
MicrosoftCertificateFromPfx(string pfxFilePath, string password = "")
{
System.Security.Cryptography.X509Certificates.X509Certificate2 cert =
new System.Security.Cryptography.X509Certificates.X509Certificate2(
pfxFilePath
, password
);
return cert;
}
}
}