Saturday, October 10, 2009

Accessing Private Key from PFX (PKCS#12) in .Net Framework

You may know that .Net Framework has X509Certificate2 class to read X.509 certificate and obtain the public key from the certificate:

var cert = new X509Certificate2("my.cer");
var key = cert.PublicKey.Key as RSACryptoServiceProvider;
var cipher = key.Encrypt(Encoding.UTF8.GetBytes(txtClear.Text), false);
txtCipher.Text = Convert.ToBase64String(cipher);

However, you may not know that X509Certificate2 class can also read the first private key from a PFX file without CryptoAPI:

var cert = new X509Certificate2("my.pfx", "password");
var key = cert.PrivateKey as RSACryptoServiceProvider;
var clear = Convert.FromBase64String(txtCipher.Text);
txtClear.Text = Encoding.UTF8.GetString(key.Decrypt(clear, false));

To read all private keys from PFX, use PKCS12.Read() method below. It uses CryptoAPI and it will return an array of X509Certificate2. You can get the private key to decrypt the cipher as shown above. The full sample code can be download at Shane's Shelf.

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;

namespace X509Cert
{
public class PKCS12
{
public static X509Certificate2[] Read(string filename, string password)
{

FileStream stream = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
stream.Close();


WIN32.CRYPT_DATA_BLOB cryptdata = new WIN32.CRYPT_DATA_BLOB();
cryptdata.cbData = buffer.Length;
cryptdata.pbData = Marshal.AllocHGlobal(cryptdata.cbData);
Marshal.Copy(buffer, 0, cryptdata.pbData, buffer.Length);
IntPtr hMemStore = WIN32.PFXImportCertStore(ref cryptdata, password, WIN32.CRYPT_USER_KEYSET);
Marshal.FreeHGlobal(cryptdata.pbData);

uint provinfosize = 0;

List<X509Certificate2> certs = new List<X509Certificate2>();

IntPtr certHandle = IntPtr.Zero;
while ((certHandle = WIN32.CertEnumCertificatesInStore(hMemStore, certHandle)) != IntPtr.Zero)
{

if (WIN32.CertGetCertificateContextProperty(certHandle, WIN32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref provinfosize))
{

IntPtr info = Marshal.AllocHGlobal((int)provinfosize);

if (WIN32.CertGetCertificateContextProperty(certHandle, WIN32.CERT_KEY_PROV_INFO_PROP_ID, info, ref provinfosize))
{
var certData = new X509Certificate2(certHandle).Export(X509ContentType.SerializedCert);
certs.Add(new X509Certificate2(certData));
}
Marshal.FreeHGlobal(info);

}
}

Marshal.FreeHGlobal(hMemStore);
return certs.ToArray();

}
}

public class WIN32
{
public const uint CRYPT_USER_KEYSET = 0x00001000;
public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;

[DllImport("crypt32.dll", SetLastError = true)]
public static extern IntPtr PFXImportCertStore(ref CRYPT_DATA_BLOB pPfx, [MarshalAs(UnmanagedType.LPWStr)] String szPassword, uint dwFlags);

[DllImport("CRYPT32.DLL", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext);

[DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);

[DllImport("advapi32.dll", EntryPoint = "CryptAcquireContext", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptAcquireContext(ref IntPtr phProv, string szContainer, string szProvider, uint dwProvType, uint dwFlags);

[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}

public WIN32()
{

}
}
}

The above code is altered base on http://www.cnblogs.com/rainlake/archive/2005/09/15/237997.html