C# 带SHA256的SignedXml计算签名

C# 带SHA256的SignedXml计算签名,c#,xml,encryption,digital-signature,C#,Xml,Encryption,Digital Signature,我正在尝试使用SHA256对XML文档进行数字签名 我正试着用这个 这是我的密码- CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription),"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); X509Certificate2 cert = new X509Certificate2(@"location of pks file", "password"); X

我正在尝试使用SHA256对XML文档进行数字签名

我正试着用这个

这是我的密码-

CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription),"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");

X509Certificate2 cert = new X509Certificate2(@"location of pks file", "password");
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(@"input.xml");

SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

// 
// Add a signing reference, the uri is empty and so the whole document 
// is signed. 
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);

// 
// Add the certificate as key info, because of this the certificate 
// with the public key will be added in the signature part. 
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature. 
signedXml.ComputeSignature();

但是我得到了“指定的算法无效”。在
signedXml.ComputeSignature()处出错。有人能告诉我我做错了什么吗?

X509Certificate2
将私钥从pfx文件加载到不支持SHA-256的Microsoft增强加密提供程序v1.0(提供程序类型
1
a.k.a.
PROV\u RSA\u FULL
)中

基于CNG的加密提供程序(在Vista和Server 2008中引入)比基于CryptoAPI的提供程序支持更多的算法,但.NET代码似乎仍然使用基于CryptoAPI的类,如
RSACng
,而不是
RSACng
,因此我们必须克服这些限制

但是,另一个CryptoAPI提供程序Microsoft增强的RSA和AES加密提供程序(提供程序类型
24
a.k.a.
PROV\u RSA\u AES
)确实支持SHA-256。因此,如果我们将私钥放入该提供者,我们可以使用它进行签名

首先,您必须调整
X509Certificate2
构造函数,以便通过添加
X509KeyStrageFlags.Exportable
标志,从
X509Certificate2
放入的提供程序中导出密钥:

X509Certificate2 cert = new X509Certificate2(
    @"location of pks file", "password",
    X509KeyStorageFlags.Exportable);
并导出私钥:

var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
    /* includePrivateParameters = */ true);
然后为支持SHA-256的提供程序创建一个新的
RSACryptServiceProvider
实例:

var key = new RSACryptoServiceProvider(
    new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
并将私钥导入其中:

key.FromXmlString(exportedKeyMaterial);
创建了
SignedXml
实例后,告诉它使用
key
而不是
cert.PrivateKey

signedXml.SigningKey = key;
现在它将起作用

以下是MSDN上的详细信息

下面是您的示例的完整调整代码:

CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");

X509Certificate2 cert = new X509Certificate2(@"location of pks file", "password", X509KeyStorageFlags.Exportable);

// Export private key from cert.PrivateKey and import into a PROV_RSA_AES provider:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString( /* includePrivateParameters = */ true);
var key = new RSACryptoServiceProvider(new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
key.FromXmlString(exportedKeyMaterial);

XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(@"input.xml");

SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = key;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

// 
// Add a signing reference, the uri is empty and so the whole document 
// is signed. 
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);

// 
// Add the certificate as key info, because of this the certificate 
// with the public key will be added in the signature part. 
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature. 
signedXml.ComputeSignature();

导出和重新导入已经完成,但还有一些其他选项需要注意

1.使用GetRSAPrivateKey和.NET 4.6.2(当前处于预览中) (extension)方法返回密钥和平台的“最佳可用类型”的RSA实例(与PrivateKey属性相反,“每个人都知道”返回RSACryptServiceProvider)

在99.99(etc)%的所有RSA私钥中,此方法返回的对象能够生成SHA-2签名

虽然在.NET 4.6(.0)中添加了该方法,但在本例中存在4.6.2的要求,因为从GetRSAPrivateKey返回的RSA实例不适用于SignedXml。自那以后(162556年)

2.在不导出的情况下重新打开密钥 一、 我个人不喜欢这种方法,因为它使用(现在是遗留的)PrivateKey属性和RSACryptServiceProvider类。但是,它具有在所有版本的.NET Framework上工作的优势(尽管在非Windows系统上不是.NET Core,因为RSACryptoServiceProvider仅适用于Windows)

如果您已经将cert.PrivateKey强制转换为RSACryptServiceProvider,则可以通过UpgradeCsp发送它。由于这是打开现有密钥,因此不会向磁盘写入额外的材料,因此它使用与现有密钥相同的权限,并且不需要您进行导出


但是(注意!)不要将PersistKeyInCsp=false设置为false,因为这将在克隆关闭时擦除原始密钥。

如果在升级到.Net 4.7.1或更高版本后遇到此问题:

.Net 4.7及以下版本:

SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
.Net 4.7.1及以上版本:

SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.GetRSAPrivateKey();

在dotnet core中,Vladimir Kocjancic的功劳如下:

var xml = new SignedXml(request) {SigningKey = privateKey};
                xml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
                xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigSHA256Url;
                xml.KeyInfo = keyInfo;
                xml.AddReference(reference);
                xml.ComputeSignature();
这是行不通的。我用了这个

var xml = new SignedXml(request) {SigningKey = privateKey};
                xml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
                xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;
                xml.KeyInfo = keyInfo;
                xml.AddReference(reference);
                xml.ComputeSignature();

已更改的签名方法=>
xml.SignedInfo.SignatureMethod=SignedXml.xmldsigrasha256url

作为次要的顺序问题,X509Certificate2.PrivateKey将密钥打开到其导入为的任何提供程序/提供程序类型。恰巧certreq生成ProviderType=1密钥,OpenSSL的默认PFX创建在导入时指定(间接)ProviderType=1存储。我通过使用OpenSSL创建PFX成功地实现了这一效果,如下所示:
OpenSSL pkcs12-export-aes256-CSP“Microsoft增强的RSA和AES加密提供程序“-in public.cer-inkey private.key-out combined.pfx
。还要注意的是,我们必须使用
MachineKeySet
标志来防止应用程序在每次加载PFX时用文件填充硬盘。仅当您对几个字节的数据进行签名时才有效。更好的方法是使用
cert.GetRSAPrivateKey()设置SigningKey
var xml = new SignedXml(request) {SigningKey = privateKey};
                xml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
                xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;
                xml.KeyInfo = keyInfo;
                xml.AddReference(reference);
                xml.ComputeSignature();