C# &引用;指定的提供程序类型无效";尝试加载证书的私钥时出现加密异常
我正在尝试读取第三方服务提供商与我共享的证书的私钥,以便在通过网络发送给他们之前,我可以使用它对一些XML进行加密。我在C#中是以编程方式这样做的,但我认为这是一个权限或配置错误的问题,因此我将重点关注似乎最相关的事实:C# &引用;指定的提供程序类型无效";尝试加载证书的私钥时出现加密异常,c#,windows,certificate,mmc,certificate-store,C#,Windows,Certificate,Mmc,Certificate Store,我正在尝试读取第三方服务提供商与我共享的证书的私钥,以便在通过网络发送给他们之前,我可以使用它对一些XML进行加密。我在C#中是以编程方式这样做的,但我认为这是一个权限或配置错误的问题,因此我将重点关注似乎最相关的事实: 我认为这个问题与代码无关;我的代码可以在其他计算机上运行,这个问题会影响来自Microsoft的示例代码 该证书作为PFX文件提供,仅用于测试目的,因此它还包括一个虚拟证书颁发机构 使用MMC.exe,我可以将证书导入本地计算机的个人存储中,然后向所有相关帐户授予私钥权限,并
- 我认为这个问题与代码无关;我的代码可以在其他计算机上运行,这个问题会影响来自Microsoft的示例代码
- 该证书作为PFX文件提供,仅用于测试目的,因此它还包括一个虚拟证书颁发机构
- 使用MMC.exe,我可以将证书导入本地计算机的个人存储中,然后向所有相关帐户授予私钥权限,并将证书颁发机构拖放到受信任的根证书颁发机构中
- 使用C#,我可以加载证书(通过其指纹识别),并使用
验证它是否有私钥。但是,尝试读取密钥会导致错误。在.NET中,当尝试访问属性X509Certificate2.HasPrivateKey
时,会引发X509Certificate2.PrivateKey
,并显示消息“指定的提供程序类型无效”。在Win32中,调用方法加密异常
返回等效的HRESULT,CryptAcquireCertificatePrivateKey
NTE\u BAD\u PROV\u TYPE
- 当使用两个Microsoft自己的代码示例读取证书的私钥时,也会发生同样的异常
- 在当前用户的等效存储(而不是本地计算机)中安装相同的证书可以成功加载私钥
- 我使用的是具有本地管理员权限的Windows 8.1,我已经尝试在正常模式和提升模式下运行我的代码。Windows 7和Windows 8上的同事已经能够从本地计算机存储加载同一证书的密钥
- 我可以成功读取自签名IIS测试证书的私钥,该证书位于同一存储位置
- 我已经瞄准了.NET4.5(在一些较旧版本的框架中报告了此错误)
- 我不认为这是证书模板的问题,因为我认为这会对本地计算机和当前用户存储产生同等的影响
- 最好使用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
的步骤:
我在自动化时遇到的另一个问题是:我们对私钥使用长时间生成的密码,密码可能包含
“
。对于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>