This blog post is about programming and its purpose is to have a link to direct developers for explanation. Inspired from this list:

A bit of history

Disclaimer: TL;DR.

Microsoft has a long history of their proprietary cryptography subsystem called CryptoAPI. It was first invented in Windows NT 4.0 in the way it is  widely used these days. *nix-based platforms use standard key storage format defined in a number of RFCs, often in an unencrypted format, PKCS#8 or PKCS#1 (which is a subset of PKCS#8). Operations with keys (encryption, signing, etc.) were implemented in separate libraries such as OpenSSL. Any library that implements PKCS#1/8 could work with such keys, thus providing a way to use custom cryptographic libraries on a single platform.

Microsoft invented and implemented their own philosophy around their cryptography subsystem with Cryptographic Service Provider (CSP) concept and proprietary key format. CSP is simply a box with named encrypted keys inside. Each CSP is responsible for key stored inside and provides an abstraction layer between client (key consumer) and certificate keys. CSP stores keys in an encrypted form, thus access to private key raw file doesn’t give you anything useful. This is how Microsoft provides a kind of key security. Instead of raw access to key material (that prevents from key leak in some degree), you use standard CryptoAPI calls and ask particular CSP to use named key to perform cryptographic operation (encryption, signing, whatever else). In some cases, you can export key material in standard format, such as PKCS#12, sometimes not. This behavior is governed by key export policy and this is another story. In theory, this concept was intended to protect keys from leaks, make developer’s life easier by abstracting cryptographic operations via a set of CryptoAPI functions (most of them are defined in crypt32.dll library) to operate with keys.

After years, with the release of Windows Vista and Windows Server 2008, Microsoft rebuilt entire cryptography stack from scratch and improved it all around, by:

  • providing key isolation
  • moving cryptography operations to kernel memory (in legacy CSP cryptographic operations were executed in user memory for software keys)
  • added built-in support and implementation for NSA Suite B algorithms (SHA2 hashing family, ECC-based asymmetric keys, AES)
  • added an ability to use custom algorithm implementations (such as GOST)
  • unified abstract functions set
  • much more

This new stack was named a Cryptography Next Generation or CNG, or CAPI2 and backed by ncrypt.dll library. Providers within this stack got new name to distinguish from legacy CSPs – Key Storage Provider or KSP. CSP –> legacy crypto, KSP –> modern crypto. Plain and simple.

Built-in Windows components and services got native support for CNG: ADCS, ADDS, EFS, IIS, RDS, Internet Explorer, etc. Almost all what was shipped with Windows OS and what wasn’t based on .NET was compatible with CNG in 2006. A limited number of external products got support for CNG. Most popular was Microsoft Office 2007 which natively supports CNG when installed on Windows Vista and newer OSes. But most external products that were either, .NET-based or had .NET interface were not compatible with CNG, because .NET didn’t support CNG at that time. And its support came many and many years after CNG become native in Windows. M(B)illions of developers and IT administrators crushed their heads while battling with keys in attempt to get the right one for their application.

It was .NET 4.6 when .NET-based life become different. Cryptography stack in .NET can be divided to two eras: before 4.6 and after.

Dark Ages (before .NET 4.6)

Before .NET Framework version 4.6, cryptography support in .NET was Windows-only and sticks to legacy CryptoAPI library calls. Easiest (and, possibly, the only) way to access the certificate’s private key was:

public class Class1 {
     public Class1() {
         var cert = new X509Certificate2(...);
         var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
         privateKey.Decrypt(...);
         // or
         privateKey.SignData(...);
     }
}

An X509Certificate2 class has a PrivateKey property of AsymmetricAlgorithm type. AsymmetricAlgorithm class is abstract class for any asymmetric algorithm and defines only few relevant methods. Access to actual algorithm implementations is done via explicit algorithm groups: RSA, DSA, ECDsa, ECDiffieHellman. These classes had only one platform-specific implementation. In case of RSA it was RSACryptoServiceProvider. So, no doubt, previous example worked in 99.99%. In very rare cases you could get InvalidCastException: in 0.009% it was ECC key and in 0.001% it was DSA key.

Note: this example throws exception, when you access PrivateKey property and private key is stored in KSP. This means that KSP keys explicitly not supported in X509Certificate2 ecosystem before .NET 4.6.

Bright Ages (after .NET 4.6)

.NET team was criticized for poor to no CNG support for a long period after CNG release. As the result, major .NET-based Microsoft products still (as of early 2020) don’t support CNG keys. Entire System Center product line, Exchange Server, ADFS and many other products still doesn’t support CNG storage, while CNG is about 14 years around us. Cool story. .NET team added a very basic CngKey class in v3.5 to access CNG storages and keys. Not so much and this class wasn’t integrated with X509Certificate2 class in any way.

Only with the release of v4.6, things become real and CNG support was greatly improved by adding CNG implementations for asymmetric key algorithms: RSACng, DSACng, ECDsaCng, ECDiffieHellmanCng. Using these classes, you can fully access CNG keys in your .NET applications. And here is a little puzzle: .NET has two implementations for RSA keys: legacy RSACryptoServiceProvider and new RSACng. You can’t know at runtime where the key is stored: in CSP or KSP and depending on key storage, a cast to different types is required. And this cast must be done at compile time, because RSA abstract class didn’t have methods to perform cryptographic operations. Kudos to .NET team since they found a quite elegant way to solve this puzzle: They added cryptographic methods to abstract base classes and now you can use them without knowing exact implementation: legacy CSP or modern KSP.

Compare them:

Instead or fixing X509Certificate.PrivateKey property, .NET team added extension methods to X509Certificate2 class:

Since 4.6, this is the recommended way to access public and private keys. What you need to know in advance – which method to call: RSA, DSA, ECDsa? You can’t know this at compile time, but you can check it at runtime by checking the algorithm OID in public key: X509Certificate2.PublicKey has Oid that identifies the asymmetric key algorithm. Drop this property to switch statement and call appropriate extension methods to get the right key.

While first example still works in 4.6, direct access to X509Certificate2.PrivateKey and X509Certificate2.PublicKey.Key properties is now discouraged. You shall access keys only via mentioned extension methods. No discussions, no exceptions. Otherwise, you are a candidate for new thread on StackOverflow as I referenced in the beginning of this post. Don’t believe? Check next section!

These changes not only solved the support of CNG in X509Certificate2 class, but helped to move this class to .NET Core and other platforms with very different cryptography subsystems and add platform-specific implementations for asymmetric algorithms. So far, so good.

Weird Ages (.NET 4.7)

In 4.7 (and all versions of .NET Core on Windows), .NET team made things even worse, by changing the default type returned by X509Certificate2.PrivateKey from RSACryptoServiceProvider to RSACng. I already mentioned that this access is strictly discouraged starting with v4.6. Applications now crash at a runtime when you attempt to use example I posted in “Dark Ages” section. That example uses compile-time explicit cast to RSACryptoServiceProvider, but SURPRISE, the type is changed to RSACng in 4.7! Check and mate.

Summary

The whole point of this post was to explain why:

public class Class1 {
     public Class1() {
         var cert = new X509Certificate2(...);
         var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
         // use key
     } }

is bad! And:

public class Class1 {
     public Class1() {
         var cert = new X509Certificate2(...);
         RSA privateKey = cert.GetRSAPrivateKey();
         // use the key
     } }

or

public class Class1 {
     public Class1() {
         const String RSA = "1.2.840.113549.1.1.1";
         const String DSA = "1.2.840.10040.4.1";
         const String ECC = "1.2.840.10045.2.1";
         var cert = new X509Certificate2(...);
         switch (cert.PublicKey.Oid.Value) {
             case RSA:
                 RSA rsa = cert.GetRSAPrivateKey(); // or cert.GetRSAPublicKey() when need public key
                 // use the key
                 break;
             case DSA:
                 DSA dsa = cert.GetDSAPrivateKey(); // or cert.GetDSAPublicKey() when need public key
                 // use the key
                 break;
             case ECC:
                 ECDsa ecc = cert.GetECDsaPrivateKey(); // or cert.GetECDsaPublicKey() when need public key
                 // use the key
                 break;
         }
     } }

is the right way to access the private key in .NET Framework 4.6+ and .NET Core (all versions). Happy programming with .NET and X509Certificate2!

6 Comments

  1. Avatar Elena on August 14, 2020 at 11:41 am

    Good day.

    Is it possible make request like this “curl –cert test.crt –key test.key” on .net framework 4.7?

    • Vadims Podāns Vadims Podāns on August 15, 2020 at 12:52 am

      Sorry, I don’t know what that command does.

  2. Avatar Abhinav on August 31, 2020 at 1:01 am

    Any idea if GetRSAPrivateKey throws Invalid provider type specified with .Net framework 4.6.1 and of course the oid value is of RSA .

    • Vadims Podāns Vadims Podāns on August 31, 2020 at 1:12 am

      Do the certificate contain the private key? What “certutil -store my ” says?

      • Avatar Abhinav on August 31, 2020 at 1:32 am

        Got he issue from another blog of yours. Certificate private key is deleted. Used crtutil to find the diff between the 2 RSACng keys. Thanks.

        • Vadims Podāns Vadims Podāns on August 31, 2020 at 1:34 am

          Good you were able to narrow the issue root.

Leave a Comment





This site uses Akismet to reduce spam. Learn how your comment data is processed.