"Invalid provider type specified" error when accessing X509Certificate2.PrivateKey on CNG certificates
You may get the following exception when trying to access X509Certificate2.PrivateKey on a .NET 3.5 (or older) app:
System.Security.Cryptography.CryptographicException: Invalid provider type specified.
at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
When this happened to me, I used my CryptoAPI Tracer on the problematic app to find out which CryptoAPI was failing and causing that exception. This was the one:
00773fb8 "Microsoft Software Key Storage P"
CryptAcquireContextA (0x448) FAILED
LastErrorValue: (HRESULT) 0x80090014 (2148073492) - Invalid provider type specified.
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
So the certificate I was using was associated to Microsoft Software Key Storage Provider, which is not a CSP but a KSP:
CNG Key Storage Providers
Unlike Cryptography API (CryptoAPI), Cryptography API: Next Generation (CNG) separates cryptographic providers from key storage providers (KSPs) . KSPs can be used to create, delete, export, import, open and store keys. Depending on implementation, they can also be used for asymmetric encryption, secret agreement, and signing. Microsoft installs the following KSPs beginning with Windows Vista and Windows Server 2008.
This is a CNG certificate! This is what CertUtil.exe returns on the private key of that certificate:
Key Container = ...
Unique container name: ...
Provider = Microsoft Software Key Storage Provider
Private key is NOT exportable
Encryption test passed
So certutil.exe knows how to deal with this kind of CNG certificates. I used my CryptoAPI Tracer on CertUtil, and this tool is using CryptAcquireCertificatePrivateKey instead of CryptAcquireContext to access the keys of the cert. CryptAcquireCertificatePrivateKey has specific flags to deal with CNG:
This function will attempt to obtain the key by using CryptoAPI. If that fails, this function will attempt to obtain the key by using the Cryptography API: Next Generation (CNG).
The pdwKeySpec variable receives the CERT_NCRYPT_KEY_SPEC flag if CNG is used to obtain the key.
The key is a CNG key.
.NET is not CNG aware yet (at least up to version 3.5 SP1) . It uses CryptAcquireContext instead of CryptAcquireCertificatePrivateKey and CryptAcquireContext has no flags to deal with CNG.
A possible workaround to this may be to use CryptoAPI/CNG API directly to deal with CNG keys. That is what certutil.exe does. But if we want an easier and pure .NET solution which understands CNG, this will help to implement it:
Security.Cryptography.dll provides a new set of algorithm implementations to augment the built in .NET framework supported algorithms. It also provides some APIs to extend the existing framework cryptography APIs. Within this project you will find:
§ A CNG implementation of the AES, RSA, and TripleDES encryption algorithms
§ A CNG implementation of a random number generator
§ A class that allows dynamically creating algorithms both from this library as well as all of the algorithms that ship with .NET 3.5
§ An enumerator over all of the installed CNG providers on the current machine
§ Extension methods that allow access to all of the keys installed in a CNG provider, as well as all of the algorithms the provider supports
If you want to use CNG with this dll, you have to do the following:
1) Change .NET version to 3.5. As far as I know the dll requires us to move at least to that version.
2) Reference Security.Cryptography.dll in your project.
3) Include "using Security.Cryptography.X509Certificates;" statement (in C#, of course) to activate CNG extensions for standard X509Certificate2 class.
Then you may check whether the cert has a CNG key using the extension .HasCngKey() for X509Certificate2. If you have a CNG key, you can then instantiate an RSACng object using the extension .GetCngPrivateKey() for X509Certificate2. You can then i.e. decrypt with RSACng.DecryptValue() .
I hope this helps.
Alex (Alejandro Campos Magencio)