SignedXml 使用 SHA256 计算签名

SignedXml Compute Signature with SHA256

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

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

这是我的代码 -

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(); 收到 "Invalid algorithm specified." 错误。谁能告诉我我做错了什么?

X509Certificate2 将私钥从 pfx 文件加载到 Microsoft Enhanced Cryptographic Provider v1.0(提供程序类型 1 a.k.a .PROV_RSA_FULL) 不支持 SHA-256.

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

但是,另一个 CryptoAPI 提供程序 Microsoft Enhanced RSA and AES Cryptographic Provider(提供程序类型 24 a.k.a。PROV_RSA_AES)确实支持SHA-256。因此,如果我们将私钥输入此提供程序,我们就可以使用它进行签名。

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

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

并导出私钥:

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

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

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

并将私钥导入其中:

key.FromXmlString(exportedKeyMaterial);

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

signedXml.SigningKey = key;

现在可以使用了。

这是 MSDN 上的 list of provider types and their codes

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

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(扩展)方法 returns "the best available type" 的 RSA 实例用于密钥和平台(相对于 PrivateKey 属性 "everyone knows" returnsRSACryptoServiceProvider).

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

虽然在 .NET 4.6(.0) 中添加了该方法,但在这种情况下存在 4.6.2 的要求,因为从 GetRSAPrivateKey 返回的 RSA 实例不适用于 SignedXml。自 been fixed (162556).

2。重新打开密钥不导出

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

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 发送它。由于这是打开一个现有的密钥,因此不会有额外的 material 写入磁盘,它使用与现有密钥相同的权限,并且不需要您进行导出。

但是(注意!)不要设置 PersistKeyInCsp=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();

感谢 Vladimir Kocjancic

在 dotnet 核心中我有这个:

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.XmlDsigRSASHA256Url