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

C#WindowsCertificateMmcCertificate Store

C# Problem Overview


I'm trying to read the private key of a certificate which has been shared with me by a third-party service provider, so I can use it to encrypt some XML before sending it to them over the wire. I'm doing so programmatically in C#, but I think this is a permissions or misconfiguration issue, so I'll focus on the facts which seem to be most relevant:

  • I don't think this issue is code-related; my code works on other computers, and the issue affects sample code from Microsoft.
  • The certificate was provided as a PFX file and is just for test purposes, so it also includes a dummy certification authority.
  • Using MMC.exe, I can import the certificate into the personal store for the local machine, before granting permissions on the private key to all relevant accounts, and dragging and dropping the certification authority into the Trusted Root Certification Authorities.
  • Using C#, I can load the certificate (identified by its thumbprint) and verify that it has a private key using X509Certificate2.HasPrivateKey. However, trying to read the key causes an error. In .NET a CryptographicException is thrown with the message "Invalid provider type specified" upon trying to access the property X509Certificate2.PrivateKey. In Win32, calling the method CryptAcquireCertificatePrivateKey returns the equivalent HRESULT, NTE_BAD_PROV_TYPE.
  • This is the same exception which also occurs when using two of Microsoft's own code samples to read the private key of the certificate.
  • Installing the same certificate in the equivalent store for the current user, instead of the local machine, allows the private key to be successfully loaded.
  • I'm on Windows 8.1 with local administrator rights, and I've tried running my code in both normal and elevated modes. Colleagues on Windows 7 and Windows 8 have been able to load the key from the local machine store for the same certificate.
  • I can successfully read the private key of the self-signed IIS test certificate, which is in the same store location.
  • I am already targeting .NET 4.5 (this error has been reported with some older versions of the framework).
  • I don't think this is a problem with certificate templates, because I would expect that to affect both the local machine and current-user stores equally?

Unlike my colleagues, I have made multiple previous attempts to uninstall and re-install the certificate in various ways, including via IIS Manager and also including an older certificate from the same issuer. I can't see any traces of old or duplicate certificates in MMC. However, I do have many private key files of identical size which, based on the last-write time, must have been left behind after my various installation attempts. These are found at the following locations, for the local machine and current user stores respectively:

c:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

c:\Users<username>\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-[rest of user ID]

So, can anyone please advise whether:

  • It's a good idea to uninstall the certificate using MMC, delete all those files which look like orphaned private keys, and then re-install the certificate and try again?
  • There any other files which I should try to manually delete?
  • There's anything else I should try?

UPDATE - Added a code sample showing an attempt to read a private key:

static void Main()
{
    // Exception occurs when trying to read the private key after loading certificate from here:
    X509Store store = new X509Store("MY", StoreLocation.LocalMachine);
    // Exception does not occur if certificate was installed to, and loaded from, here:
    //X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
  
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
    X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Test Certificate Select", "Select a certificate from the following list to get information on that certificate", X509SelectionFlag.MultiSelection);
    Console.WriteLine("Number of certificates: {0}{1}", scollection.Count, Environment.NewLine);

    foreach (X509Certificate2 x509 in scollection)
    {
        try
        {
            Console.WriteLine("Private Key: {0}", x509.HasPrivateKey ? x509.PrivateKey.ToXmlString(false) : "[N/A]");
            x509.Reset();
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    store.Close();

    Console.ReadLine();
}

C# Solutions


Solution 1 - C#

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 - C#

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 - C#

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 - C#

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.

Solution 5 - C#

The link to Alejandro's blog is key.

I believe this is because the certificate is stored on your machine with the CNG ("Crypto Next-Generation") API. The old .NET API is not compatible with it, so it doesn't work.

You can use the Security.Cryptography wrapper for this API (available on Codeplex). This adds extension methods to X509Certificate/X509Certificate2, so your code will look something like:

using Security.Cryptography.X509Certificates; // Get extension methods

X509Certificate cert; // Populate from somewhere else...
if (cert.HasCngKey())
{
    var privateKey = cert.GetCngPrivateKey();
}
else
{
    var privateKey = cert.PrivateKey;
}

Unfortunately the object model for CNG private keys is quite a bit different. I'm not sure if you can export them to XML like in your original code sample...in my case I just needed to sign some data with the private key.

Solution 6 - C#

Using Visual Studio 2019 and IISExpress, I was able to correct this problem by removing the following flag when loading the .pfx|.p12 file:

X509KeyStorageFlags.MachineKeySet

Before:

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

After:

X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

Normally, I'll load the cert this way:

var myCert = new X509Certificate2("mykey.pfx", "mypassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

This does NOT raise an Exception, instead the Exception is raised when attempting to use the certificate (or in my case, when trying to obtain the .PrivateKey). I've found this issue can be caused when the invoking user has insufficient permissions.

Since I rely on the MachineKeySet flag for some environments, my current solution is to swallow the Exception, change the flags, try again. :/

A more organic solution would be to test the permissions level and set this flag dynamically, however I'm not aware of a simple way to do this, hence the fallback.

NOTE: The .pfx file I'm using was created from a website that uses a JavaScript library (digitalbazaar/forge) which does NOT use CNG (Cryptography Next Generation) Key Storage Providers. here are many common causes for this same error (the most common fixes related to CNG extensions, which have -- unfortunately even changed namespaces in .NET versions), which throw the same error. Microsoft should ultimately be more verbose when throwing these types of errors.

Solution 7 - C#

What worked for me: IIS / Application pool / Load User Profile = true

Solution 8 - C#

As many other answers have pointed out, this issue arises when the private key is a Windows Cryptography: Next Generation (CNG) key instead of a "classic" Windows Cryptographic API (CAPI) key.

Beginning with .NET Framework 4.6 the private key (assuming it's an RSA key) can be accessed via an extension method on X509Certificate2: cert.GetRSAPrivateKey().

When the private key is held by CNG the GetRSAPrivateKey extension method will return an RSACng object (new to the framework in 4.6). Because CNG has a pass-through to read older CAPI software keys, GetRSAPrivateKey will usually return an RSACng even for a CAPI key; but if CNG can't load it (e.g. it's an HSM key with no CNG driver) then GetRSAPrivateKey will return an RSACryptoServiceProvider.

Note that the return type for GetRSAPrivateKey is RSA. Beginning with .NET Framework v4.6 you shouldn't need to cast beyond RSA for standard operations; the only reason to use RSACng or RSACryptoServiceProvider is when you need to interop with programs or libraries that use the NCRYPT_KEY_HANDLE or the key identifier (or opening a persisted key by name). (.NET Framework v4.6 had a lot of places that still cast the input object to RSACryptoServiceProvider, but those were all eliminated by 4.6.2 (of course, that's more than 2 years ago at this point)).

ECDSA certificate support was added in 4.6.1 via a GetECDsaPrivateKey extension method, and DSA was upgraded in 4.6.2 via GetDSAPrivateKey.

On .NET Core the return value from Get[Algorithm]PrivateKey changes depending on the OS. For RSA it's RSACng/RSACryptoServiceProvider on Windows, RSAOpenSsl on Linux (or any UNIX-like OS except macOS), and a non-public type on macOS (meaning you can't cast it beyond RSA).

Solution 9 - C#

I also had this issue and after attempting the suggestions in this post without success. I was able to resolve my issue by reloading the certificate with the Digicert certificate utility https://www.digicert.com/util/. This allows one to select the provider to load the certificate into. In my case loading the certificate into the Microsoft RSA Schannel Cryptographic Provider provider where I had expected it to be in the first place resolved the issue.

Solution 10 - C#

Powershell version of the answer from @berend-engelbrecht, assuming openssl installed via chocolatey

function Fix-Certificates($certPasswordPlain)
{
    $certs = Get-ChildItem -path "*.pfx" -Exclude "*.converted.pfx"
    $certs | ForEach-Object{
        $certFile = $_

        $shortName = [io.path]::GetFileNameWithoutExtension($certFile.Name)
        Write-Host "Importing $shortName"
        $finalPfx = "$shortName.converted.pfx"


        Set-Alias openssl "C:\Program Files\OpenSSL\bin\openssl.exe"

        # Extract public key
        OpenSSL pkcs12 -in $certFile.Fullname -nokeys -out "$shortName.cer" -passin "pass:$certPasswordPlain"

        # Extract private key
        OpenSSL pkcs12 -in $certFile.Fullname -nocerts -out "$shortName.pem" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain"

        # Convert private key to RSA format
        OpenSSL rsa -inform PEM -in "$shortName.pem" -out "$shortName.rsa" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain" 2>$null

        # Merge public keys with RSA private key to new PFX
        OpenSSL pkcs12 -export -in "$shortName.cer" -inkey "$shortName.rsa" -out $finalPfx -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain"

        # Clean up
        Remove-Item "$shortName.pem"
        Remove-Item "$shortName.cer"
        Remove-Item "$shortName.rsa"

        Write-Host "$finalPfx created"
    }
}

# Execute in cert folder
Fix-Certificates password

Solution 11 - C#

I faced the same problem in our IIS app:

 System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent)
    System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent)
    System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent)

Regenerating certs as mentioned here didnt help. I also noticed that test console app works fine under pool user.

Problem disappeared after clearing "Enable 32-bit Applications" setting for IIS app pool.

Solution 12 - C#

For a .net 4.8 WCF service I was getting this error because i hadn't (also) set the httpRuntime version.

Without the httpRuntime set i was getting the error whenever a secured .net tcp binding was present.

<system.web>
    <compilation targetFramework="4.8" />
    <httpRuntime targetFramework="4.8" />
    <!--<customErrors mode="Off" />-->
</system.web>

I had already setup the required permissions in mmc.exe -> add snap-in -> certificates -> personal -> all tasks -> manage private keys -> add [computer name]\IIS_IUSRS (all my application pools are ApplicationPoolIdentity [default] - which is covered by IIS_IUSRS)

Solution 13 - C#

the PrivateKey and PublicKey properties are outdated beginning with NET 4.6. Please use:

  • cert.GetRSAPrivateKey() or GetECDsaPrivateKey()
  • cert.GetRSAPublicKey() or GetECDsaPublicKey()

Regards

Solution 14 - C#

After following the accepted answer (specifying KeySpec), the exception changed to System.Security.Cryptography.CryptographicException: Invalid provider type specified.. I resolved that exception by giving my web application access to the private key (IIS_IUSRS). I discovered that this would also fix the problem with my original certificate. So before generating and deploying new certificates, check the private key's permissions as well.

Solution 15 - C#

The problem is your code is not able to read the PFX file. convert the pfx file to RSA format by doing the below steps.

Get the certificate and extract the pfx file from the certificate.

use the password 123456 below to have a quick solution.

rename your pfx to 'my.pfx' file to make it simple and put it in "C:\Certi" make sure you have open SSL installed already in the system. open cmd in windows system and type --> OpenSSL Stay cool and just run these ones by one --> copy paste. *Note > -passin is your Pfx file password > -passout is the new password for converted pfx.

1. pkcs12 -in "C:\Certi\my.pfx" -nokeys -out "C:\Certi\MYCERT.cer" -passin "pass:123456"

2. pkcs12 -in "C:\Certi\my.pfx" -nocerts –out “C:\Certi\MYCERT.pem" -passin "pass:123456" -passout "pass:123456"

3. rsa -inform PEM -in "C:\Certi\MYCERT.pem" -out "C:\Certi\MYCERT.rsa" -passin "pass:123456" -passout "pass:123456"

if you face issue in the 3rd command go here https://decoder.link/converter
Click PKC#12 To PEM upload your pfx file and get it converted online. download the zip file. it contains 3 files. just copy ".key" file and rename it as my.key and put in "C:\Certi"

4. rsa -in C:\Certi\my.key -out C:\Certi\domain-rsa.key

5. pkcs12 -export -in "C:\Certi\MYCERT.cer" -inkey "C:\Certi\domain-rsa.key" -out "C:\Certi\CONVERTED.pfx" -passin "pass:123456" -passout "pass:123456"

 
**Also, you can try below things if the issue still persists**
  • Given access to app pool or IIS user to folder "mandatory to do"

Path --- > C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

  • Delete the old keys (clear up the mess)
    Path --- > C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

Solution 16 - C#

This error went away after i removed the certificate from the store and imported it again from the .pfx file, using the Certificate Import Wizard (double-click the .pfx file) and an extra import option.

After checking the import option (same step where password is entered):

> "Mark this key as exportable. This will allow you to back up or transport your keys at a later time."

The privatekey could now be accessed from code without any errors.

I also explicitly select the "Personal" store on the 2nd to last step, but I don't think this matters.

Solution 17 - C#

I tried everything in this post, but for me the solution was:

  1. Import the original .p12 file into my local machine

  2. Export it as a .pfx file checking the "Export all extended properties" and "Include all the certificates in the certification path if its possible" options

  3. Read the new certificate using this flag options from this post solution:

    var certificate = new X509Certificate2(certKeyFilePath, passCode,
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet |       
    X509KeyStorageFlags.PersistKeySet );
    

Solution 18 - C#

It seems on Windows 10, if I run my program without administrative privileges (whether I use the PrivateKey property or the GetRSAPrivateKey() extension method), I will see this exception (see the title of this discussion thread for the exception). If I run my program with administrative privileges and use the PrivateKey property, I will also see this exception. Only if I run my program with administrative privileges and use the GetRSAPrivateKey() extension method, I will not see this exception.

Solution 19 - C#

I also had this issue, I changed the '.jks' to '.pfx' without typing the password, once I changed the key store type again with the password it is worked successfully.

Solution 20 - C#

After experiencing this issue on some computers in a security-critical environment, I found that, contrary to several answers, updating the Cryptographic Service Provider (CSP) of the certificate from the old CAPI standard to the new CNG standard resolved this issue.

The security policies probably restricted the use of CSPs relying on deprecated cryptographic algorithms.

Updating the CSP to Microsoft Software Key Storage Provider based on these instructions fixed the issues.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSimon13View Question on Stackoverflow
Solution 1 - C#Berend EngelbrechtView Answer on Stackoverflow
Solution 2 - C#enablorView Answer on Stackoverflow
Solution 3 - C#Dhanuka777View Answer on Stackoverflow
Solution 4 - C#sportsView Answer on Stackoverflow
Solution 5 - C#Joe StrommenView Answer on Stackoverflow
Solution 6 - C#tresfView Answer on Stackoverflow
Solution 7 - C#Michael AguilarView Answer on Stackoverflow
Solution 8 - C#bartonjsView Answer on Stackoverflow
Solution 9 - C#HughView Answer on Stackoverflow
Solution 10 - C#fiatView Answer on Stackoverflow
Solution 11 - C#Vadim KiselevView Answer on Stackoverflow
Solution 12 - C#DavidGView Answer on Stackoverflow
Solution 13 - C#Martin.MartinssonView Answer on Stackoverflow
Solution 14 - C#BoukeView Answer on Stackoverflow
Solution 15 - C#Manoj KargetiView Answer on Stackoverflow
Solution 16 - C#TikallView Answer on Stackoverflow
Solution 17 - C#rasputinoView Answer on Stackoverflow
Solution 18 - C#robbie fanView Answer on Stackoverflow
Solution 19 - C#SelwadeView Answer on Stackoverflow
Solution 20 - C#thabsView Answer on Stackoverflow