我在使用已安装的证书签署xml文档时遇到问题。我尝试过使用证书文件(.pfx)在X509Certificate2的LocalMachine、CurrentUser和Initialize实例中安装证书。每个都有自己的问题;我倾向于使用安装在LocalMachine存储中的证书。下面我概述了三种方法以及每种方法的关注点:
StoreLocation LocalMachine-首选方法
var certStore = new X509Store(StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbPrint, true);
var myCert = certCollection[0];
// I can get the correct certificate but the following line throws "Invalid provider type specified." error
var SigningKey = myCert.PrivateKey;
StoreLocation当前用户
var certStore = new X509Store(StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbPrint, true);
var myCert = certCollection[0];
// I can get the correct certificate but the following line throws "Keyset does not exist" error
var SigningKey = myCert.PrivateKey;
只有更改以下文件夹%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys的权限,我才能获得PrivateKey。这看起来不是实现签名的正确方式。
使用证书文件
var certificateFile = @"C:CertificateFolderAuthorizedCertificate.pfx";
var myCert = new X509Certificate2(certificateFile, password, X509KeyStorageFlags.UserKeySet);
这种方法有效,但是我必须提供证书文件和密码,这是不可取的。
如何让第一个方法(LocalMachine)工作?建议/最佳做法是什么
以下代码用于对xml文档进行签名,以供参考
private void SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(xmlDoc);
// Add the key to the SignedXml document.
signedXml.SigningKey = cert.PrivateKey;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
var env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Include the public key of the certificate in the assertion.
signedXml.KeyInfo = new KeyInfo();
signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.WholeChain));
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
}
LocalMachine存储版本
更换
var SigningKey = myCert.PrivateKey;
带有
var SigningKey = myCert.GetRSAPrivateKey();
(s/RSA/DSA/视情况而定)
PrivateKey属性只能返回可转换为RSACryptoServiceProvider或DSACryptoServiceProvider的密钥"指定的提供程序类型无效";意味着您的私钥存储在CNG中,而不是CAPI中。
只有在安装了.NET 4.6.2或更高版本的情况下,这才会起作用,因为此时SignedXml及其助手类中的某些限制(关于非RSACryptoServiceProvider RSA)已经修复。
(替代方案:升级到Windows 10,操作系统在CNG桥上添加了一个CAPI来解决这个问题)
CurrentUser存储版本
此版本失败,因为当您从PFX导入证书时,您使用MachineKeySet导入了它(或者您没有指定UserKeySet,它以前是从计算机密钥存储中导出的)。用户存储中的证书副本表明它的私钥存在于机器存储中。而且,出于某种原因,你无法访问它。("出于某种原因",因为通常这意味着你不能添加它…)
PFX版本
这是因为PFX规定密钥应该存储在CAPI CSP中(PFX携带大量元数据),从而允许证书PrivateKey属性发挥作用。