“尝试加载证书的私钥时,指定了无效的提供程序类型”CryptographicException

时间:2014-03-22 18:33:14

标签: c# windows certificate mmc certificate-store

我正在尝试读取第三方服务提供商与我共享的证书的私钥,因此我可以使用它来加密某些XML,然后再将其发送给他们。我在C#中以编程方式这样做,但我认为这是一个权限或配置错误的问题,因此我将重点关注似乎最相关的事实:

  • 我不认为这个问题与代码有关;我的代码适用于其他计算机,该问题会影响Microsoft的示例代码。
  • 证书是作为PFX文件提供的,仅用于测试目的,因此它还包括虚拟证书颁发机构。
  • 使用MMC.exe,我可以将证书导入本地计算机的个人存储区,然后将私钥授予所有相关帐户,并将证书颁发机构拖放到受信任的根证书颁发机构。
  • 使用C#,我可以加载证书(由其指纹识别)并使用X509Certificate2.HasPrivateKey验证它是否具有私钥。但是,尝试读取密钥会导致错误。在.NET中,在尝试访问属性CryptographicException时,会抛出X509Certificate2.PrivateKey,并显示消息“指定了无效的提供程序类型”。在Win32中,调用方法CryptAcquireCertificatePrivateKey将返回等效的HRESULT,NTE_BAD_PROV_TYPE
  • 当使用两个Microsoft自己的代码示例来读取证书的私钥时,也会出现同样的异常。
  • 在当前用户(而不是本地计算机)的等效存储中安装相同的证书,可以成功加载私钥。
  • 我使用的是具有本地管理员权限的Windows 8.1,并且我尝试在正常模式和高级模式下运行我的代码。 Windows 7和Windows 8上的同事已能够从本地计算机商店加载密钥以获得相同的证书。
  • 我可以成功读取自签名IIS测试证书的私钥,该证书位于同一个商店位置。
  • 我已经针对.NET 4.5(该框架的某些旧版本已报告此错误。)
  • 我认为这不是证书模板的问题,因为我希望这会同时影响本地计算机和当前用户存储?

与我的同事不同,我之前曾多次尝试以各种方式卸载和重新安装证书,包括通过IIS管理器,还包括来自同一发行人的旧证书。我在MMC中看不到任何旧证书或重复证书的痕迹。但是,我确实有许多相同大小的私钥文件,基于最后写入时间,在我的各种安装尝试后必须留下。这些位于以下位置,分别针对本地计算机和当前用户存储:

C:\ ProgramData \微软\加密\ RSA \ MachineKeys的

c:\ Users \\ AppData \ Roaming \ Microsoft \ Crypto \ RSA \ S-1-5-21- [其余用户ID]

那么,有人可以建议是否:

  • 最好使用MMC卸载证书,删除所有看起来像孤立私钥的文件,然后重新安装证书再试一次?
  • 我应该尝试手动删除任何其他文件吗?
  • 还有什么我应该尝试的吗?

更新 - 添加了一个代码示例,显示尝试读取私钥:

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();
}

18 个答案:

答案 0 :(得分:28)

我在Windows 8和Server 2012/2012 R2上遇到了同样的问题,我最近收到了两个新证书。在Windows 10上,问题不再出现(但这对我没有帮助,因为在服务器上使用了操纵证书的代码)。虽然Joe Strommen的解决方案原则上有效,但不同的私钥模型需要使用证书对代码进行大量更改。我发现更好的解决方案是将私钥从CNG转换为RSA,如Remy Blok here所述。

Remy使用OpenSSL和两个较旧的工具来完成私钥转换,我们希望自动化它并开发一个仅支持OpenSSL的解决方案。给定CNG格式的私钥密码MYCERT.pfxMYPWD,这些是使用RSA格式的私钥和相同密码获取新CONVERTED.pfx的步骤:

  1. 提取公钥,完整的证书链:
  2. OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"

    1. 提取私钥:
    2. OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out “MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"

      1. 将私钥转换为RSA格式:
      2. OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"

        1. 将具有RSA私钥的公钥合并到新的PFX:
        2. OpenSSL pkcs12 -export -in "MYCERT.cer" -inkey "MYCERT.rsa" -out "CONVERTED.pfx" -passin "pass:MYPWD" -passout "pass:MYPWD"

          如果您加载转换后的pfx或将其导入Windows证书库而不是CNG格式pfx,则问题就会消失,C#代码无需更改。

          在自动执行此操作时遇到的另一个问题是:我们使用长密码生成私钥,密码可能包含"。对于OpenSSL命令行,密码中的"个字符必须转义为""

答案 1 :(得分:9)

Alejandro's blog的链接很关键。

我相信这是因为证书存储在您的计算机上,使用CNG("加密下一代")API。旧的.NET API与它不兼容,因此它无法正常工作。

您可以对此API使用Security.Cryptography包装器(available on Codeplex)。这会将扩展方法添加到X509Certificate/X509Certificate2,因此您的代码将类似于:

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;
}

不幸的是,CNG私钥的对象模型有点不同。我不确定您是否可以像原始代码示例一样将它们导出到XML ...在我的情况下,我只需要使用私钥对某些数据进行签名。

答案 2 :(得分:6)

这是另一个可能发生这种情况的原因, 这是一个奇怪的问题,经过一天的努力,我解决了这个问题。 作为实验,我更改了“C:\ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys”文件夹的权限,该文件夹使用机器密钥库保存证书的私钥数据。当您更改此文件夹的权限时,所有私钥都显示为“Microsoft Software KSP提供程序”,而不是提供程序(在我的情况下,它们应该是“Microsoft RSA Schannel Cryptographic Provider”)。

解决方案:重置对Machinekeys文件夹的权限

可以在here中找到此文件夹的原始权限。在我的情况下,我已经更改了“Everyone”的权限,在删除“特殊权限”时给出了读取权限。所以我查看了我的团队成员之一(右键单击文件夹>属性>安全性>高级>选择“所有人”>编辑>点击权限复选框列表中的“高级设置”

Special permissions

希望这会拯救某人的一天!

这是我找到答案source的地方,归功于他记录这一点。

答案 3 :(得分:3)

就我而言,我试图通过PowerShell的New-SelfSignedCertificate命令使用自签名证书。默认情况下,它将使用CNG(下一代加密货币)API代替较旧/经典的加密CAPI生成证书。一些较旧的代码会遇到麻烦;在我的情况下,它是IdentityServer STS提供程序的旧版本。

通过在New-SelfSignedCertificate命令的末尾添加此代码,我克服了这个问题:

-KeySpec KeyExchange

有关powershell命令的开关的参考:

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

答案 4 :(得分:2)

许多其他答案指出,当私钥是Windows加密:下一代(CNG)密钥而不是“经典” Windows加密API(CAPI)密钥时,就会出现此问题。

从.NET Framework 4.6开始,可以通过X509Certificate2上的扩展方法:cert.GetRSAPrivateKey()访问私钥(假设它是RSA密钥)。

当CNG持有私钥时,GetRSAPrivateKey扩展方法将返回一个RSACng对象(4.6中的新功能)。由于CNG具有读取旧版CAPI软件密钥的传递功能,因此GetRSAPrivateKey通常即使对于CAPI密钥也将返回RSACng;但是如果CNG无法加载它(例如,它是没有CNG驱动程序的HSM密钥),那么GetRSAPrivateKey将返回RSACryptoServiceProvider

请注意,GetRSAPrivateKey的返回类型为RSA。从.NET Framework v4.6开始,对于标准操作,您不需要强制转换到RSA以上;使用RSACngRSACryptoServiceProvider的唯一原因是当您需要与使用NCRYPT_KEY_HANDLE或密钥标识符的程序或库进行互操作时(或通过名称打开持久的密钥)。 (.NET Framework v4.6在很多地方仍将输入对象强制转换为RSACryptoServiceProvider,但这些都被4.6.2淘汰了(当然,这已经超过2年了))。

ECDSA证书支持是通过GetECDsaPrivateKey扩展方法在4.6.1中添加的,DSA是通过GetDSAPrivateKey在4.6.2中升级的。

在.NET Core上,Get[Algorithm]PrivateKey的返回值取决于操作系统。对于RSA,在Windows上为RSACng / RSACryptoServiceProvider,在Linux(或macOS以外的任何类似UNIX的操作系统)上为RSAOpenSsl,在macOS上为非公共类型(这意味着您无法进行强制转换超出RSA)。

答案 5 :(得分:1)

我也遇到了这个问题,在尝试了这篇文章中的建议后没有成功。我可以通过使用Digicert证书实用程序https://www.digicert.com/util/重新加载证书来解决我的问题。这允许选择要将证书加载到的提供程序。在我的情况下,将证书加载到 Microsoft RSA Schannel加密提供程序提供程序中,我原本预计它将首先解决问题。

答案 6 :(得分:1)

在我的情况下,以下代码在localhost(NET 3.5和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...

但是在部署到certificate.PrivateKey的Azure Web应用程序时失败了

它通过如下更改代码来工作:

 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...

感谢Microsoft Azure,整整一天的工作都失去了。

答案 7 :(得分:1)

我在IIS应用程序中遇到了同样的问题:

 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)

按此处所述重新生成证书没有帮助。 我还注意到,测试控制台应用程序在池用户下运行良好。

清除IIS应用程序池的“启用32位应用程序”设置后,问题消失了。

答案 8 :(得分:1)

对于.net 4.8 WCF服务,由于没有(同时)设置httpRuntime版本,因此出现此错误。

没有httpRuntime设置,只要存在安全的.net tcp绑定,我都会收到错误消息。

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

我已经在mmc.exe中设置了必需的权限->添加管理单元->证书->个人->所有任务->管理私钥->添加[计算机名] \ IIS_IUSRS(我所有的应用程序池都是ApplicationPoolIdentity [默认]-由IIS_IUSRS涵盖)

答案 9 :(得分:0)

遵循可接受的答案(指定KeySpec)之后,异常更改为System.Security.Cryptography.CryptographicException: Invalid provider type specified.。我通过为Web应用程序授予对私钥(IIS_IUSRS)的访问权限来解决了该异常。我发现这也可以解决我的原始证书问题。因此,在生成和部署新证书之前,还要检查私钥的权限。

答案 10 :(得分:0)

从NET 4.6开始, PrivateKey和PublicKey属性已过时。请使用:

  • cert。 GetRSAPrivateKey ()或 GetECDsaPrivateKey ()
  • cert。 GetRSAPublicKey ()或 GetECDsaPublicKey ()

致谢

答案 11 :(得分:0)

在Windows 10上,如果我在没有管理特权的情况下运行程序(无论是使用PrivateKey属性还是GetRSAPrivateKey()扩展方法),我都会看到此异常(有关异常,请参见此讨论线程的标题) 。如果我以管理特权运行程序并使用PrivateKey属性,我还将看到此异常。仅当我以管理特权运行程序并使用GetRSAPrivateKey()扩展方法时,才会看到此异常。

答案 12 :(得分:0)

我尝试了这篇文章中的所有内容,但是对我来说解决方案是:

  1. 将原始.p12文件导入本地计算机
  2. 将其导出为.pfx文件,并选中“导出所有扩展属性”和“如果可能的话,将所有证书包括在证书路径中”
  3. 使用以下post解决方案中的以下标志选项读取新证书:

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

答案 13 :(得分:0)

使用证书导入向导(双击.pfx文件)和额外的导入选项,从商店中删除证书并再次从.pfx文件中导入证书后,此错误消失了。

在检查导入选项之后(输入密码的相同步骤):

“将此密钥标记为可导出。这将允许您在以后备份或传输密钥。”

现在可以从代码中正确访问私钥了。

我也明确选择了第二步到最后一步的“个人”商店,但我认为这并不重要。

答案 14 :(得分:0)

问题是您的代码无法读取PFX文件。 通过执行以下步骤将pfx文件转换为RSA格式。

获取证书并从证书中提取pfx文件。

使用下面的密码123456可以快速解决问题。

将pfx重命名为“ my.pfx”文件以使其简单,然后将其放在“ C:\ Certi” 确保您已经在系统中安装了开放SSL。 在Windows系统中打开cmd并键入-> OpenSSL 保持冷静,然后一键运行这些->复制粘贴。 *注意

-密码是您的Pfx文件密码 - passout 是转换后的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"

如果您在第3条命令中遇到问题,请转到此处https://decoder.link/converter
单击PKC#12到PEM 上传您的pfx文件并在线进行转换。 下载zip文件。 它包含3个文件。只需复制“ .key”文件并将其重命名为my.key并放入“ 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**
  • 授予对应用程序池或IIS用户的访问权限,以使其“必须执行”文件夹

路径---> C:\ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys

  • 删除旧键(清理混乱)
    路径-> C:\ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys

答案 15 :(得分:0)

对我有用的是: IIS /应用程序池/加载用户配置文件= true

答案 16 :(得分:0)

使用Visual Studio 2019和IISExpress,我能够通过在加载.pfx|.p12文件时删除以下标志来纠正此问题:

X509KeyStorageFlags.MachineKeySet

之前:

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

之后:

X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

通常,我将通过以下方式加载证书:

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

这不会引发异常,而是在尝试使用证书时(或者在我的情况下,试图获取.PrivateKey时引发异常)。我发现这个问题可能是由于invoking user has insufficient permissions而引起的。

由于在某些环境下我依靠MachineKeySet标志,所以我当前的解决方案是吞下Exception,更改标志,然后重试。 :/

更有机的解决方案是测试权限级别并动态设置此标志,但是我不知道执行此操作的简单方法,因此是后备方法。

注意::我正在使用的.pfx文件是从使用JavaScript库(digitalbazaar/forge)的网站创建的,该网站不使用CNG(下一代密码学)密钥存储提供程序。这是导致同一错误的许多常见原因(与CNG扩展名有关的最常见修复程序,不幸的是,甚至更改了.NET版本中的名称空间),它们也会引发相同的错误。最终,Microsoft在引发这些类型的错误时应该更加冗长。

答案 17 :(得分:0)

来自@ berend-engelbrecht的答案的Powershell版本,假设openssl通过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