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

Sim*_*n13 37 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();
}
Run Code Online (Sandbox Code Playgroud)

Ber*_*cht 34

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

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

  1. 提取公钥,完整的证书链:

OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"

  1. 提取私钥:

OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out “MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. 将私钥转换为RSA格式:

OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. 将具有RSA私钥的公钥合并到新的PFX:

OpenSSL pkcs12 -export -in "MYCERT.cer" -inkey "MYCERT.rsa" -out "CONVERTED.pfx" -passin "pass:MYPWD" -passout "pass:MYPWD"

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

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

  • 这解决了我的问题,希望我能在几个小时前找到它! (2认同)
  • @Berend,上面步骤1创建了.cert文件。Step2没有创建.pem,没有显示错误,但显示“ pkcs12:使用-help进行摘要”。我究竟做错了什么? (2认同)

spo*_*rts 13

就我而言,以下代码在 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...
Run Code Online (Sandbox Code Playgroud)

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

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

 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...
Run Code Online (Sandbox Code Playgroud)

由于 Microsoft Azure,我的生活中又一次浪费了一整天的工作。

  • 添加 `X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable` 在 Azure 上为我工作 (2认同)

Mic*_*lar 13

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


Joe*_*men 9

链接到亚历杭德罗的博客是关键.

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

您可以使用此API的Security.Cryptography包装器(可在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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这仍然是最新.NET(4.6)的方法吗? (6认同)

bar*_*njs 8

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

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

当 CNG 持有私钥时,GetRSAPrivateKey扩展方法将返回一个RSACng对象(4.6 中框架的新增内容)。由于 CNG 具有读取较旧 CAPI 软件密钥的直通功能,GetRSAPrivateKey因此通常会RSACng为 CAPI 密钥返回偶数;但如果 CNG 无法加载它(例如,它是没有 CNG 驱动程序的 HSM 密钥),则将GetRSAPrivateKey返回RSACryptoServiceProvider.

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

在 4.6.1 中通过扩展方法添加了 ECDSA 证书支持GetECDsaPrivateKey,在 4.6.2 中通过GetDSAPrivateKey.

在 .NET Core 上,返回值会Get[Algorithm]PrivateKey根据操作系统的不同而变化。对于 RSA,它在 Windows、 Linux(或除 macOS 之外的任何类 UNIX 操作系统)上为RSACng/ ,在 macOS 上为非公共类型(这意味着您无法将其转换为超出)。RSACryptoServiceProviderRSAOpenSslRSA


Dha*_*777 7

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

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

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

特殊权限

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

这是我找到答案来源的地方,归功于他记录这一点.


小智 5

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

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

-KeySpec KeyExchange

powershell命令在交换机上的参考:

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

  • 爱你 !谢谢 ;) (2认同)
  • 正好在现场,谢谢!事实上,除了 None 之外的所有选项都适用于 -KeySpec 参数,例如:New-SelfSignedCertificate -DnsName "IssuedToName" -CertStoreLocation "cert:\CurrentUser\My" -KeySpec Signature` (2认同)