Bouncy Castle: Convert a BouncyCastle AsymmetricKeyEntry to a .NET AsymmetricAlgorithm


After extending my Certificate Generator tool to integrate directly with the Encryption Management product I work with I ran into a problem with converting private keys to a format that the .NET X509Certificate2 class could understand. .NET crypto does not 'like' the format Bouncy Castle uses to represent key data.

This is a case where better documentation for Bouncy Castle would have been great. I'm sure this must be a common issue.

References:

 

As you can see from the references above, there are a number of pages out there on the interwebs that discuss issues with Converting Bouncy Castle private keys and X509Certificate2 objects. As I investigated the solutions, only one of them seemed to work for me.

I was working on a program that would automate the following tasks:

  • Generate a cetificate and private key
  • Store them in a PKCS12 (p12 format) container
  • Use an API for the Encryption Management product I work with to import the p12 / pfx data

The first 2 tasks were pretty simple (See this previous article for details), but I ran into problems importing the PKCS#12 data into the product. There is an API that should decode the data and store it in an X509Certificate2 object for import into the database. This API is a thin wrapper around the X509Certificate2 Constructor. Unfortunately the resulting .NET Certificate object did not have the Private Key. It threw a CryptographicException and told me that the keyset does not exist.

I knew the message to be factually incorrect- the PKCS12 store DID have a private key. I could prove this by writing the byte array to disk and double clicking on it in Windows. Windows imported it into my personal store WITH the private key. Something was up with the .NET crypto and I didn't have much time to figure it out.

Rather than spend a lot of time trying to fix the problem with .NET (Or refactor my code to make it more coupled and less portable) I turned to BouncyCastle and had it decode the private key with the intention to convert it over to a type usable by X509Certificate2. Here is the somewhat crude solution that is working for me until I can either refactor it or fix .NET (could be some security issue- I've seen that in the past...):

 

X509Certificate2 certificate; // Certificate + Private key should wind up in here
certificate = CUSTOMPKCS12EXTRACTOR.CertAndKeyExtractor(cert, password); // Proprietary method call (Name changed to protect the innocent). :)

//Use Bouncy Castle to read the private key since MS isn't cooperating
using (MemoryStream ms = new MemoryStream())
{
  ms.Write(cert, 0, cert.Length); // cert is a byte[] of the PKCS#12 store
  ms.Position = 0; // Put stream pointer back to position 0
  Org.BouncyCastle.Pkcs.Pkcs12Store st = new Org.BouncyCastle.Pkcs.Pkcs12Store(ms, "keystorePassword".ToCharArray());

  foreach (var alias in st.Aliases) // read through aliases looking for Private keys
  {
    if (st.IsKeyEntry((string)alias))
    {
      Org.BouncyCastle.Pkcs.AsymmetricKeyEntry keyEntry = st.GetKey(alias.ToString());
      RSACryptoServiceProvider intermediateProvider = (RSACryptoServiceProvider)Org.BouncyCastle.Security.DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyEntry.Key);

      RSACryptoServiceProvider csp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider", new Guid().ToString(), new System.Security.AccessControl.CryptoKeySecurity(), null));
      csp.ImportCspBlob(intermediateProvider.ExportCspBlob(true));
      certificate.PrivateKey = csp;
      break; // Since my tool only makes PKCS12 with one key, I can stop once it is found
    }
  }
}


Full credit for the private key conversion goes to Cabadam [social.msdn.com]. It worked for me! Here is what I understand of what is going on (I'm no crypto expert- so I can only really speak to what my code does. Trying to get a better grasp on .NET crypto in general...):

  • I generate a Bouncy Castle Pkcs12Store from the encoded bytes:

    Org.BouncyCastle.Pkcs.Pkcs12Store st = new Org.BouncyCastle.Pkcs.Pkcs12Store(ms, "keystorePassword".ToCharArray());
  • The code loops over all the entries in the PKCS#12 store until it finds the private key:

    foreach (var alias in st.Aliases) // read through aliases looking for Private keys
    {
    if (st.IsKeyEntry((string)alias))
    {
       // Do work here (Described above and below)...
    }
    }



  • The Private key is captured into a BC AsymmericKeyEntry object:

    Org.BouncyCastle.Pkcs.AsymmetricKeyEntry keyEntry = st.GetKey(alias.ToString());


  • An intermediate RSACryptoServiceProvider is setup from the key stored in the AsymmetricKeyEntry object. This is done with the help of the DotNetUtilities class provided by Bouncy Castle. This provider is used later on:

    RSACryptoServiceProvider intermediateProvider = (RSACryptoServiceProvider)Org.BouncyCastle.Security.DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyEntry.Key);


  • Another RSACryptoServiceProvider  is created. This one is initialized directly (rather than using data generated from Bouncy Castle). It appears to be setup to hold the key data in a way that can be used with the rest of the .NET crypto framework?:

    RSACryptoServiceProvider csp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider", new Guid().ToString(), new System.Security.AccessControl.CryptoKeySecurity(), null));


  • The first CryptoServiceProvider exports its data into the second one. Then the certificate is directly assigned to the second CryptoServiceProvider. There are no red lines in Visual studio and the thing compiles- the types must match up!

    csp.ImportCspBlob(intermediateProvider.ExportCspBlob(true));
    certificate.PrivateKey = csp;

 

While I do use the .NET crypto providers I still have a ways to go before I feel like I wholly 'understand' them. This solution worked for me and I hope it can be helpful for someone else someday.