11

I thought this would be straightforward but apparently it isn't. I have a certificate installed that has a private key, exportable, and I want to programmatically export it with the public key ONLY. In other words, I want a result equivalent to selecting "Do not export the private key" when exporting through certmgr and exporting to .CER.

It seems that all of the X509Certificate2.Export methods will export the private key if it exists, as PKCS #12, which is the opposite of what I want.

Is there any way using C# to accomplish this, or do I need to start digging into CAPICOM?

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Aaronaught
  • 115,846
  • 24
  • 251
  • 329

3 Answers3

19

For anyone else who might have stumbled on this, I figured it out. If you specify X509ContentType.Cert as the first (and only) parameter to X509Certificate.Export, it only exports the public key. On the other hand, specifying X509ContentType.Pfx includes the private key if one exists.

I could have sworn that I was seeing different behaviour last week, but I must have already had the private key installed when I was testing. When I deleted that certificate today and started again from scratch, I saw that there was no private key in the exported cert.

Aaronaught
  • 115,846
  • 24
  • 251
  • 329
  • do you know if there is a way to export only the private key without the whole certificate?, I have to extricate the private key as byte array, and I dont find any way to do it.... – RRR Oct 24 '11 at 08:34
  • 3
    @RRR: Whatever it is that you're trying to do, I'd advise against it because the "private key" of a certificate is a lot more than just a byte array, it's a cryptographic *algorithm*, specifically an `AsymmetricAlgorithm`, and different certificates may have completely different algorithms. If you lose this information, it will be very difficult to reconstruct and decrypt/verify anything encrypted/signed by the public key. If you really want to try to mess with it, look at `X509Certificate2.PrivateKey` and work from there. – Aaronaught Oct 24 '11 at 13:43
  • 2
    @Aaronaught: You generally never want to export the private key along with the certificate. The private key must remain a closely held secret. You can verify anything signed with the private key having only the certificate -- certificates only contain the public key, and this is all that is needed to verify a signature. You generally do not want to use the private key to encrypt data. Also, the private and public keys are not interchangeable -- given a public key it is close to impossible to guess the private key, but not vice versa. So, keep that private key at home. – Jim Flood Jan 17 '14 at 19:30
  • Note that this yields a different file format, so its no longer stored as a `.pfx`/`.p12` file. – Peter Jan 02 '18 at 14:40
10

I found the following program helpful for reassuring myself that the RawData property of the certificate contains only the public key (MSDN is unclear on this), and that the answer above regarding X509ContentType.Cert vs. X509ContentType.Pfx works as expected:

using System;
using System.Linq;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;

class Program
{
    static void Main( string[] args )
    {
        var certPath = @"C:\blah\somecert.pfx";
        var certPassword = "somepassword";

        var orig = new X509Certificate2( certPath, certPassword, X509KeyStorageFlags.Exportable );
        Console.WriteLine( "Orig   : RawData.Length = {0}, HasPrivateKey = {1}", orig.RawData.Length, orig.HasPrivateKey );

        var certBytes = orig.Export( X509ContentType.Cert );
        var certA = new X509Certificate2( certBytes );
        Console.WriteLine( "cert A : RawData.Length = {0}, HasPrivateKey = {1}, certBytes.Length = {2}", certA.RawData.Length, certA.HasPrivateKey, certBytes.Length );

        // NOTE that this the only place the byte count differs from the others
        certBytes = orig.Export( X509ContentType.Pfx );
        var certB = new X509Certificate2( certBytes );
        Console.WriteLine( "cert B : RawData.Length = {0}, HasPrivateKey = {1}, certBytes.Length = {2}", certB.RawData.Length, certB.HasPrivateKey, certBytes.Length );

        var keyIdentifier = ( new X509SecurityToken( orig ) ).CreateKeyIdentifierClause<X509RawDataKeyIdentifierClause>();
        certBytes = keyIdentifier.GetX509RawData();
        var certC = new X509Certificate2( certBytes );
        Console.WriteLine( "cert C : RawData.Length = {0}, HasPrivateKey = {1}, certBytes.Length = {2}", certC.RawData.Length, certC.HasPrivateKey, certBytes.Length );

        Console.WriteLine( "RawData equals original RawData: {0}", certC.RawData.SequenceEqual( orig.RawData ) );

        Console.ReadLine();
    }
}

It outputs the following:

Orig   : RawData.Length = 1337, HasPrivateKey = True
cert A : RawData.Length = 1337, HasPrivateKey = False, certBytes.Length = 1337
cert B : RawData.Length = 1337, HasPrivateKey = True, certBytes.Length = 3187
cert C : RawData.Length = 1337, HasPrivateKey = False, certBytes.Length = 1337
RawData equals original RawData: True
explunit
  • 18,299
  • 5
  • 63
  • 91
1

There is an OpenSSL .NET wrapper you may find useful.

Nathan
  • 2,023
  • 1
  • 14
  • 18