C# &引用;指定的提供程序类型无效";尝试加载证书的私钥时出现加密异常

C# &引用;指定的提供程序类型无效";尝试加载证书的私钥时出现加密异常,c#,windows,certificate,mmc,certificate-store,C#,Windows,Certificate,Mmc,Certificate Store,我正在尝试读取第三方服务提供商与我共享的证书的私钥,以便在通过网络发送给他们之前,我可以使用它对一些XML进行加密。我在C#中是以编程方式这样做的,但我认为这是一个权限或配置错误的问题,因此我将重点关注似乎最相关的事实: 我认为这个问题与代码无关;我的代码可以在其他计算机上运行,这个问题会影响来自Microsoft的示例代码 该证书作为PFX文件提供,仅用于测试目的,因此它还包括一个虚拟证书颁发机构 使用MMC.exe,我可以将证书导入本地计算机的个人存储中,然后向所有相关帐户授予私钥权限,并

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

  • 我认为这个问题与代码无关;我的代码可以在其他计算机上运行,这个问题会影响来自Microsoft的示例代码
  • 该证书作为PFX文件提供,仅用于测试目的,因此它还包括一个虚拟证书颁发机构
  • 使用MMC.exe,我可以将证书导入本地计算机的个人存储中,然后向所有相关帐户授予私钥权限,并将证书颁发机构拖放到受信任的根证书颁发机构中
  • 使用C#,我可以加载证书(通过其指纹识别),并使用
    X509Certificate2.HasPrivateKey
    验证它是否有私钥。但是,尝试读取密钥会导致错误。在.NET中,当尝试访问属性
    X509Certificate2.PrivateKey
    时,会引发
    加密异常
    ,并显示消息“指定的提供程序类型无效”。在Win32中,调用方法
    CryptAcquireCertificatePrivateKey
    返回等效的HRESULT,
    NTE\u BAD\u PROV\u TYPE
  • 当使用两个Microsoft自己的代码示例读取证书的私钥时,也会发生同样的异常
  • 在当前用户的等效存储(而不是本地计算机)中安装相同的证书可以成功加载私钥
  • 我使用的是具有本地管理员权限的Windows 8.1,我已经尝试在正常模式和提升模式下运行我的代码。Windows 7和Windows 8上的同事已经能够从本地计算机存储加载同一证书的密钥
  • 我可以成功读取自签名IIS测试证书的私钥,该证书位于同一存储位置
  • 我已经瞄准了.NET4.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();
}
链接到是关键

我相信这是因为证书是用CNG(“加密下一代”)API存储在您的机器上的。旧的.NETAPI与之不兼容,因此无法工作

您可以使用此API()的Security.Cryptography包装器。这会将扩展方法添加到
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…在我的例子中,我只需要使用私钥对一些数据进行签名。

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

Remy使用OpenSSL和两个较旧的工具来完成私钥转换,我们希望将其自动化,并开发了一个只使用OpenSSL的解决方案。给定具有CNG格式的私钥密码的
MYCERT.pfx
,以下是获得具有RSA格式私钥和相同密码的新
转换.pfx
的步骤:

  • 提取公钥,完整证书链:
  • 提取私钥:
  • 将私钥转换为RSA格式:
  • 将公钥与RSA私钥合并到新的PFX:
  • 如果加载转换后的pfx或将其导入Windows证书存储区而不是CNG格式的pfx,则问题将消失,C#代码无需更改


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

    ,这是发生这种情况的另一个原因, 这是一个奇怪的问题,经过一天的努力,我解决了这个问题。 作为一个实验,我更改了“C:\ProgramData\Microsoft\Crypto\RSA\MachineKe的权限
    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"
    
    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
    
     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...
    
     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...
    
     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)
    
    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);
    
    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"
    
    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**
    
    var certificate = new X509Certificate2(certKeyFilePath, passCode,
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet |       
    X509KeyStorageFlags.PersistKeySet );
    
    <system.web>
        <compilation targetFramework="4.8" />
        <httpRuntime targetFramework="4.8" />
        <!--<customErrors mode="Off" />-->
    </system.web>