"Invalid provider type specified" CryptographicException when trying to load private key of certificate

Solution 1:

I had the same problem on Windows 8 and Server 2012/2012 R2 with two new certificates I recently received. On Windows 10, the problem no longer occurs (but that does not help me, as the code manipulating the certificate is used on a server). While the solution of Joe Strommen in principle works, the different private key model would require massive change to the code using the certificates. I find that a better solution is to convert the private key from CNG to RSA, as explained by Remy Blok here.

Remy uses OpenSSL and two older tools to accomplish the private key conversion, we wanted to automate it and developed an OpenSSL-only solution. Given MYCERT.pfx with private key password MYPWD in CNG format, these are the steps to get a new CONVERTED.pfx with private key in RSA format and same password:

  1. Extract public keys, full certificate chain:
OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"
  1. Extract private key:
OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts -out "MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"
  1. Convert private key to RSA format:
OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"
  1. Merge public keys with RSA private key to new PFX:
OpenSSL pkcs12 -export -in "MYCERT.cer" -inkey "MYCERT.rsa" -out "CONVERTED.pfx" -passin "pass:MYPWD" -passout "pass:MYPWD"

If you load the converted pfx or import it in the Windows certificate store instead of the CNG format pfx, the problem goes away and the C# code does not need to change.

One additional gotcha that I encountered when automating this: we use long generated passwords for the private key and the password may contain ". For the OpenSSL command line, " characters inside the password must be escaped as "".

Solution 2:

In my case, I was trying to use a self-signed certificate with PowerShell's New-SelfSignedCertificate command. By default, it will generate a certificate using the CNG (Crypto-Next Generation) API instead of the older/classic crypto CAPI. Some older pieces of code will have trouble with this; in my case it was an older version of the IdentityServer STS provider.

By adding this at the end of my New-SelfSignedCertificate command, I got past the issue:

-KeySpec KeyExchange

Reference on the switch for the powershell command:

https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps

Solution 3:

Here is another reason that this can happen, this was a weird issue and after struggling for a day I solved the issue. As an experiment I changed permission for "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" folder which holds private key data for certificates using Machine key store. When you change permission for this folder all privatekeys shows up as "Microsoft Software KSP provider" which is not the provider (in my case they are supposed to be " Microsoft RSA Schannel Cryptographic Provider").

Solution: Reset permissions to Machinekeys folder

Original permission for this folder can be found in here. In my case I have changed permission for "Everyone", gave read permissions where it removed "Special permissions" tick. So I checked with one of my team member (Right click folder> Properties > Security > Advanced > select "Everyone" > Edit > Click "Advanced settings" in permission check box list

Special permissions

Hope this will save someone's day!

Here is where I found the answer source, credit goes to him for documenting this.

Solution 4:

In my case, the following code worked fine in localhost (both NET 3.5 and NET 4.7):

 var certificate = new X509Certificate2(certificateBytes, password);

 string xml = "....";
 XmlDocument xmlDocument = new XmlDocument();
 xmlDocument.PreserveWhitespace = true;
 xmlDocument.LoadXml(xml);

 SignedXml signedXml = new SignedXml(xmlDocument);
 signedXml.SigningKey = certificate.PrivateKey;

 //etc...

But it failed when deployed to an Azure Web App, at certificate.PrivateKey

It worked by changing the code as follows:

 var certificate = new X509Certificate2(certificateBytes, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
                                                                   //^ Here
 string xml = "....";
 XmlDocument xmlDocument = new XmlDocument();
 xmlDocument.PreserveWhitespace = true;
 xmlDocument.LoadXml(xml);

 SignedXml signedXml = new SignedXml(xmlDocument);
 signedXml.SigningKey = certificate.GetRSAPrivateKey();
                                      // ^ Here too

 //etc...

A whole day of work lost thanks to Microsoft Azure, once again in my life.