View  Edit  Attributes  History  Attach  Print  Search

PFX (PKCS#12) with CryptoServiceProvider (CSP) and CryptoAPI in .Net Framework

The CryptoServiceProvider provide abstraction allowing us to encrypt/decrypt with symmetric and asymmetric keys. Personal Information Exchange (PFX or PKCS#12) is the standard to store private keys. However, it is hard to find any documentation in MSDN about PFX format, regardless PFX is invented by Microsoft.

Note: key container and key store are exchangeable below

Encrypt

To start with, we load an X.509 certificate with X509Certificate2. Then take the public key to encrypt some data.

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);

Decrypt

Going through all constructors of X509Certificate2, there is a constructor taking two parameters while one of them is a password. Here is the remarks stated in MSDN:

This constructor creates a new X509Certificate2 object using a certificate file name and a password needed to access the certificate. It is used with PKCS12 (PFX) files that contain the certificate's private key. Calling this constructor with the correct password decrypts the private key and saves it to a key container.

So, X509Certificate2 reads both X.509 certificate and PFX file. Parsing the file format will depends on which constructor we call. Now, we can get the private key to decrypt the cipher.

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));

Getting All Private Keys

Since PFX is actually a PKCS#12 container, there can be more than one keys. We could assume X509Certificate2 will only get the first private key. What about the rest? It is quite interesting there is a piece of code circulates among some Chinese web sites showing how to read the certificate in X509Certificate class with CryptoAPI. I have modified the code so that the private keys are read into X509Certificate2.

CryptoAPI

First we create a wrapper class to import the necessary functions from CryptoAPI.

using System;
using System.Runtime.InteropServices;

namespace X509Cert
{
    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()
        {

        }
    }
}

PKCS12 Key Container Reader

Then, we create a class to read the PKCS12 key container and get the array of X509Certificate2.

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();

        }
    }
}

What's Next?

We have the X509Certificate2 and we know how to get the private key. So, we only need to choose the right private key (brute force or ask user) to decrypt the data. I have created a sample that includes the executable, a PFX file (i.e. PKCS#12 key container with password="password"), two X.509 certificates. The PFX file is generated by Java keytool to demonstrate the interoperability.

[ Download ]