关于c#:SignedXml使用SHA256计算签名

SignedXml Compute Signature with SHA256

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

我正在尝试为此使用Security.Cryptography.dll。

这是我的代码-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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又称为PROV_RSA_FULL)中。

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

但是,另一个CryptoAPI提供程序Microsoft增强型RSA和AES加密提供程序(提供程序类型24又称PROV_RSA_AES)确实支持SHA-256。因此,如果将私钥输入该提供程序,则可以对其进行签名。

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

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

并导出私钥:

1
2
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
    /* includePrivateParameters = */ true);

然后为支持SHA-256的提供者创建一个新的RSACryptoServiceProvider实例:

1
2
3
var key = new RSACryptoServiceProvider(
    new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;

并将私钥导入其中:

1
key.FromXmlString(exportedKeyMaterial);

创建SignedXml实例后,告诉它使用key而不是cert.PrivateKey

1
signedXml.SigningKey = key;

它现在将起作用。

这是MSDN上提供程序类型及其代码的列表。

这是您的示例的完整调整后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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(当前处于预览状态)

GetRSAPrivateKey(扩展名)方法为密钥和平台返回"最佳类型"的RSA实例(与"每个人都知道"的私钥属性返回RSACryptoServiceProvider相对)。

在所有RSA私钥的99.99%等中,此方法返回的对象能够进行SHA-2签名生成。

在.NET 4.6(.0)中添加了该方法后,在这种情况下仍存在4.6.2的要求,因为从GetRSAPrivateKey返回的RSA实例不适用于SignedXml。此后已解决(162556)。

2.重新打开密钥而不导出

我个人不喜欢这种方法,因为它使用了(现在是传统)PrivateKey属性和RSACryptoServiceProvider类。但是,它具有可以在所有版本的.NET Framework上工作的优势(尽管非Windows系统上不是.NET Core,因为RSACryptoServiceProvider仅限于Windows)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static RSACryptoServiceProvider UpgradeCsp(RSACryptoServiceProvider currentKey)
{
    const int PROV_RSA_AES = 24;
    CspKeyContainerInfo info = currentKey.CspKeyContainerInfo;

    // WARNING: 3rd party providers and smart card providers may not handle this upgrade.
    // You may wish to test that the info.ProviderName value is a known-convertible value.

    CspParameters cspParameters = new CspParameters(PROV_RSA_AES)
    {
        KeyContainerName = info.KeyContainerName,
        KeyNumber = (int)info.KeyNumber,
        Flags = CspProviderFlags.UseExistingKey,
    };

    if (info.MachineKeyStore)
    {
        cspParameters.Flags |= CspProviderFlags.UseMachineKeyStore;
    }

    if (info.ProviderType == PROV_RSA_AES)
    {
        // Already a PROV_RSA_AES, copy the ProviderName in case it's 3rd party
        cspParameters.ProviderName = info.ProviderName;
    }

    return new RSACryptoServiceProvider(cspParameters);
}

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

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