Export private/public keys from X509 certificate to PEM

Viewed 28707

is there any convenient way to export private/public keys from .p12 certificate in PEM format using .NET Core? Without manipulating with bytes at low level? I googled for hours and almost nothing is usable in .net core or it isn't documented anywhere..

Let's have an X509Certificate2

var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far

And now I need to export the keys as two separate PEM keys. I already tried PemWriter in BouncyCastle but the types are not compatibile with System.Security.Cryptography from Core... no luck.

In other words, I'm finding a way how to write this:

$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts

Does anybody have an idea?

Thanks

4 Answers

I figured out a solution that works well. I could not find an EXACT example of how to go from certificate store to pem file in windows. Granted, this may not work for some certificates, but if you are working with one you have created yourself (for example, if you just need security between two machines you control that the end user won't see) this is a good way of going back to pem / pk (linux style).

I utilized the utilities found at http://www.bouncycastle.org/csharp/

X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);

X509Certificate2 caCert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "3C97BF2632ACAB5E35B48CB94927C4A7D20BBEBA", true)[0];


RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)caCert.PrivateKey;


AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(pkey);
using (TextWriter tw = new StreamWriter("C:\\private.pem"))
{
    PemWriter pw = new PemWriter(tw);
    pw.WriteObject(keyPair.Private);
    tw.Flush();
}

X509certificate2 -> Private, Public and Cert pems...I just discovered that you can do it in 5 or 6 lines of layman's code!

There is a free package called Chilkat (It has some chill branding). It has some very intuitive Certificate Classes Here is some example code on how to create a self signed pfx formatted certificate and export it to PEM! So that is taking a X509Certificate2 instance with a certificate and associated public key and the privatekey that signed it, and then exporting it as three separate Pem files. One for the certificate(includes public key), one for the public key, and one for the private key. Very easy (took a week of reading to figure this out, haha). And then checkout https://github.com/patrickpr/YAOG for a nice OpenSSL windows Gui for viewing/creating certs (as seen in the result screenshot).

using Chilkat;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;


namespace CertPractice
{
static public class CertificateUtilityExample
{

    public static X509Certificate2 GenerateSelfSignedCertificate()
    {


        string secp256r1Oid = "1.2.840.10045.3.1.7";  //oid for prime256v1(7)  other identifier: secp256r1
        
        string subjectName = "Self-Signed-Cert-Example";

        var ecdsa = ECDsa.Create(ECCurve.CreateFromValue(secp256r1Oid));

        var certRequest = new CertificateRequest($"CN={subjectName}", ecdsa, HashAlgorithmName.SHA256);

        //add extensions to the request (just as an example)
        //add keyUsage
        certRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));

        X509Certificate2 generatedCert = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddYears(10)); // generate the cert and sign!
//----------------end certificate generation, ie start here if you already have an X509Certificate2 instance----------------


        X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); //has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request...

        Chilkat.Cert chilkatVersionOfPfxGeneratedCert = new Chilkat.Cert(); // now use Chilcat Cert to get pems
        chilkatVersionOfPfxGeneratedCert.LoadPfxData(generatedCert.Export(X509ContentType.Pfx), null); // export as binary pfx to load into a Chilkat Cert

        PrivateKey privateKey = chilkatVersionOfPfxGeneratedCert.ExportPrivateKey(); // get the private key
        privateKey.SavePemFile(@"filepath"); //save the private key to a pem file
        
        Chilkat.PublicKey publicKey = chilkatVersionOfPfxGeneratedCert.ExportPublicKey(); //get the public key
        publicKey.SavePemFile(true, @"filepath"); //save the public key

        chilkatVersionOfPfxGeneratedCert.ExportCertPemFile(@"filepath"); //save the public Cert to pem file
        


        return pfxGeneratedCert;


    }
}

Screen shot of output pem files

Based on @bartonjs knowledge (his answer), I have written a small class that should be easy to use.

So there is now also a complete example, without having to use external dlls/nuget packages

The only changes I had to make was:

  • I had to add this "X509KeyStorageFlags.Exportable" to the StorageFlags when creating the X509Certificate2 instance, so that the method "ExportPkcs8PrivateKey()" does not fail.

With my class it is possible to convert Let's Encrypt certificates from PFX format to PEM format with certificate & private key.

How to use my class

var certificateLogic = new CertificateLogic("fileName.pfx", "privateKeyOfPfx");
certificateLogic.LoadCertificate();
certificateLogic.GenerateSaveCertificatePem();
certificateLogic.GenereateSavePrivateKeyPem();

My Code behind that class

public class CertificateLogic {

    private readonly FileInfo CertificateFile;
    private readonly SecureString CertificatePassword;
    public X509Certificate2 Certificate { get; private set; }

    public CertificateLogic(FileInfo certificationFile, string password) {
        if (!certificationFile.Exists) {
            throw new FileNotFoundException(certificationFile.FullName);
        }

        CertificateFile = certificationFile;
        CertificatePassword = ConvertPassword(password);
    }

    public CertificateLogic(string certificationFullFileName, string password) {
        var certificateFile = new FileInfo(certificationFullFileName);
        if (certificateFile == null || !certificateFile.Exists) {
            throw new FileNotFoundException(certificationFullFileName);
        }

        CertificateFile = certificateFile;
        CertificatePassword = ConvertPassword(password);
    }

    private static SecureString ConvertPassword(string password) {
        var secure = new SecureString();
        foreach (char c in password) {
            secure.AppendChar(c);
        }

        return secure;
    }

    public void LoadCertificate() {
        LoadCertificate(X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    }

    public void LoadCertificate(X509KeyStorageFlags keyStorageFlags) {
        Certificate = new X509Certificate2(CertificateFile.FullName, CertificatePassword, keyStorageFlags);
    }

    public byte[] GenerateCertificatePem() {
        var certData = Certificate.RawData;
        var newPemData = PemEncoding.Write("CERTIFICATE", certData);

        return newPemData.Select(c => (byte)c).ToArray();
    }

    public byte[] GeneratePrivateKeyPem() {
        var privateCertKey = Certificate.GetRSAPrivateKey();
        var privateCertKeyBytes = privateCertKey.ExportPkcs8PrivateKey();

        char[] newPemData = PemEncoding.Write("PRIVATE KEY", privateCertKeyBytes);

        return newPemData.Select(c => (byte)c).ToArray();
    }

    public FileInfo GenerateSaveCertificatePem() {
        var newData = GenerateCertificatePem();

        var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
        var newCertPemFile = new FileInfo($@"{CertificateFile.DirectoryName}\{oldFile} Certificate.pem");
        return SaveNewCertificate(newCertPemFile, newData);
    }

    public FileInfo GenereateSavePrivateKeyPem() {
        var newData = GeneratePrivateKeyPem();

        var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
        var newPrivateKeyPemFile = new FileInfo($@"{CertificateFile.DirectoryName}\{oldFile} PrivateKey.pem");
        return SaveNewCertificate(newPrivateKeyPemFile, newData);
    }

    public FileInfo SaveNewCertificate(FileInfo newFile, byte[] data) {
        File.WriteAllBytes(newFile.FullName, data);

        return newFile;
    }
}
Related