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 t
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.
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
).
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
Hope this will save someone's day!
Here is where I found the answer source, credit goes to him for documenting this.
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**
Path --- > C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
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:
OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"
OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out “MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"
OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"
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 ""
.
What worked for me: IIS / Application pool / Load User Profile = true