67

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

  • 我认为这个问题与代码无关;我的代码可以在其他计算机上运行,​​并且该问题会影响 Microsoft 的示例代码。
  • 该证书以 PFX 文件的形式提供,仅用于测试目的,因此它还包括一个虚拟证书颁发机构。
  • 使用 MMC.exe,我可以将证书导入本地计算机的个人存储中,然后将私钥的权限授予所有相关帐户,并将证书颁发机构拖放到受信任的根证书颁发机构中。
  • 使用 C#,我可以加载证书(由其指纹识别)并使用X509Certificate2.HasPrivateKey. 但是,尝试读取密钥会导致错误。在 .NETCryptographicException中,在尝试访问属性时会抛出消息“指定的提供程序类型无效” 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\Microsoft\Crypto\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();
}
4

20 回答 20

49

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

Remy 使用 OpenSSL 和两个较旧的工具来完成私钥转换,我们希望将其自动化并开发了一个仅限 OpenSSL 的解决方案。使用CNG格式MYCERT.pfx的私钥密码MYPWD,这些是获取新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 命令行,"密码中的字符必须转义为"".

于 2015-12-05T09:08:25.990 回答
31

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

通过在我的 New-SelfSignedCertificate 命令的末尾添加这个,我解决了这个问题:

-KeySpec 密钥交换

关于powershell命令的开关参考:

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

于 2019-04-22T20:18:24.760 回答
15

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

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

可以在此处找到此文件夹的原始权限。就我而言,我已经更改了“所有人”的权限,在删除“特殊权限”勾选的地方给予了读取权限。因此,我与我的一名团队成员进行了核对(右键单击文件夹>属性>安全>高级>选择“所有人”>编辑>单击权限复选框列表中的“高级设置”

特殊权限

希望这可以挽救某人的一天!

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

于 2017-01-11T23:56:14.247 回答
14

就我而言,以下代码在 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...

但是在部署到 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...

多亏了 Microsoft Azure,一整天的工作又一次在我的生活中消失了。

于 2019-03-25T21:42:05.177 回答
10

Alejandro 博客的链接是关键。

我相信这是因为证书使用 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;
}

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

于 2015-07-10T21:41:04.967 回答
4

使用 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)。我发现当调用用户权限不足时可能会导致此问题。

由于我在某些环境中依赖MachineKeySet标志,因此我当前的解决方案是吞下异常,更改标志,再试一次。:/

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

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

于 2019-11-01T18:40:13.660 回答
3

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

于 2019-11-20T22:49:37.587 回答
3

正如许多其他答案所指出的那样,当私钥是 Windows Cryptography: Next Generation (CNG) 密钥而不是“经典” Windows Cryptographic 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使用or的唯一原因是当您需要与使用或 密钥标识符(或按名称打开持久密钥)的RSACryptoServiceProvider程序或库进行互操作时。NCRYPT_KEY_HANDLE(.NET Framework v4.6 有很多地方仍然将输入对象转换为RSACryptoServiceProvider,但这些都被 4.6.2 淘汰了(当然,此时已经是 2 年多以前了))。

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

在 .NET Core 上,返回值Get[Algorithm]PrivateKey会因操作系统而异。对于 RSA,它在 Windows、 Linux(或除 macOS 之外的任何类似 UNIX 的操作系统)上是RSACng/ ,在 macOS 上是非公共类型(这意味着你不能将其转换为超越)。RSACryptoServiceProviderRSAOpenSslRSA

于 2018-10-04T20:38:31.127 回答
1

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

于 2017-09-25T03:14:19.857 回答
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 位应用程序”设置后问题消失。

于 2019-05-21T07:24:53.203 回答
1

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

于 2020-11-11T09:59:12.313 回答
1

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

  • 证书。GetRSAPrivateKey () 或GetECDsaPrivateKey ()
  • 证书。GetRSAPublicKey () 或GetECDsaPublicKey ()

问候

于 2020-11-03T12:24:02.433 回答
1

来自@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
于 2018-03-08T10:02:10.957 回答
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 覆盖)

于 2020-09-10T22:41:26.630 回答
0

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

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

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

现在可以从代码中访问私钥而不会出现任何错误。

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

于 2020-01-30T14:21:01.597 回答
0

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

于 2020-03-16T13:49:08.487 回答
0

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

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

    var certificate = new X509Certificate2(certKeyFilePath, passCode,
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet |       
    X509KeyStorageFlags.PersistKeySet );
    
于 2020-02-05T11:37:39.037 回答
0

在安全关键环境中的某些计算机上遇到此问题后,我发现与几个答案相反,将证书的加密服务提供商 (CSP) 从旧的 CAPI 标准更新为新的 CNG 标准解决了这个问题。

安全策略可能限制了依赖已弃用加密算法的 CSP 的使用。

根据这些说明将 CSP 更新为Microsoft 软件密钥存储提供程序解决了这些问题。

于 2021-11-03T14:36:15.517 回答
0

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

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

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

将您的 pfx 重命名为“my.pfx”文件以使其简单化并将其放在“C:\Certi”中,确保您已在系统中安装了打开的 SSL。在 windows 系统中打开 cmd 并输入 --> OpenSSL 保持冷静,一个接一个地运行这些 --> 复制粘贴。*笔记

- passin 是您的 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"

如果您在第三个命令中遇到问题,请转到此处https://decoder.link/converter
单击 PKC#12 到 PEM 上传您的 pfx 文件并在线转换。下载压缩文件。它包含 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
于 2020-01-09T15:56:19.500 回答
0

我也遇到了这个问题,我在没有输入密码的情况下将“.jks”更改为“.pfx”,一旦我再次使用密码更改了密钥存储类型,它就可以成功运行。

于 2021-10-18T08:21:45.193 回答