简体   繁体   English

SignedXml 使用 SHA256 计算签名

[英]SignedXml Compute Signature with SHA256

I am trying to digitally sign a XML document using SHA256.我正在尝试使用 SHA256 对 XML 文档进行数字签名。

I am trying to use Security.Cryptography.dll for this.我正在尝试为此使用Security.Cryptography.dll

Here is my code -这是我的代码 -

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();

But i am getting "Invalid algorithm specified."但我收到“指定的算法无效”。 error at signedXml.ComputeSignature();signedXml.ComputeSignature();处出错signedXml.ComputeSignature(); . . Can anyone tell me what I am doing wrong?谁能告诉我我做错了什么?

X509Certificate2 loads the private key from the pfx file into the Microsoft Enhanced Cryptographic Provider v1.0 (provider type 1 aka PROV_RSA_FULL ) which doesn't support SHA-256. X509Certificate2将私钥从 pfx 文件加载到不支持 SHA-256 的Microsoft Enhanced Cryptographic Provider v1.0 (provider type 1 aka PROV_RSA_FULL )中。

The CNG-based cryptographic providers (introduced in Vista and Server 2008) support more algorithms than the CryptoAPI-based providers, but the .NET code still seems to be working with CryptoAPI-based classes like RSACryptoServiceProvider rather than RSACng so we have to work around these limitations.基于 CNG 的加密提供程序(在 Vista 和 Server 2008 中引入)比基于 CryptoAPI 的提供程序支持更多的算法,但是 .NET 代码似乎仍然使用基于 CryptoAPI 的类,例如RSACryptoServiceProvider而不是RSACng因此我们必须解决这些限制。

However, another CryptoAPI provider, Microsoft Enhanced RSA and AES Cryptographic Provider (provider type 24 aka PROV_RSA_AES ) does support SHA-256.但是,另一个 CryptoAPI 提供程序, Microsoft Enhanced RSA 和 AES Cryptographic Provider (提供程序类型24又名PROV_RSA_AES )确实支持 SHA-256。 So if we get the private key into this provider, we can sign with it.因此,如果我们将私钥获取到此提供程序中,我们就可以使用它进行签名。

First, you'll have to adjust your X509Certificate2 constructor to enable the key to be exported out of the provider that X509Certificate2 puts it into by adding the X509KeyStorageFlags.Exportable flag:首先,您必须调整X509Certificate2构造函数,以便通过添加X509KeyStorageFlags.Exportable标志,将密钥从X509Certificate2放入的提供程序中导出:

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

And export the private key:并导出私钥:

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

Then create a new RSACryptoServiceProvider instance for a provider that supports SHA-256:然后为支持 SHA-256 的提供程序创建一个新的RSACryptoServiceProvider实例:

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

And import the private key into it:并将私钥导入其中:

key.FromXmlString(exportedKeyMaterial);

When you've created your SignedXml instance, tell it to use key rather than cert.PrivateKey :创建SignedXml实例后,告诉它使用key而不是cert.PrivateKey

signedXml.SigningKey = key;

And it will now work.它现在可以工作了。

Here are the list of provider types and their codes on MSDN.以下是 MSDN 上提供程序类型及其代码列表

Here's the full adjusted code for your example:这是您的示例的完整调整代码:

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();

Exporting and re-importing has already been given as an answer , but there are a couple other options that you should be aware of.导出和重新导入已作为答案给出,但您应该注意其他几个选项。

1. Use GetRSAPrivateKey and .NET 4.6.2 (currently in preview) 1. 使用 GetRSAPrivateKey 和 .NET 4.6.2(目前为预览版)

The GetRSAPrivateKey (extension) method returns an RSA instance of "the best available type" for the key and platform (as opposed to the PrivateKey property which "everyone knows" returns RSACryptoServiceProvider). GetRSAPrivateKey (扩展)方法为密钥和平台返回“最佳可用类型”的 RSA 实例(而不是“每个人都知道”的 PrivateKey 属性返回 RSACryptoServiceProvider)。

In 99.99(etc)% of all RSA private keys the returned object from this method is capable of doing SHA-2 signature generation.在 99.99(etc)% 的所有 RSA 私钥中,此方法返回的对象能够生成 SHA-2 签名。

While that method was added in .NET 4.6(.0) the requirement of 4.6.2 exists in this case because the RSA instance returned from GetRSAPrivateKey didn't work with SignedXml.虽然该方法是在 .NET 4.6(.0) 中添加的,但在这种情况下存在 4.6.2 的要求,因为从 GetRSAPrivateKey 返回的 RSA 实例不适用于 SignedXml。 That has since been fixed (162556).此后已修复(162556)。

2. Re-open the key without export 2. 重新打开key不导出

I, personally, don't like this approach because it uses the (now-legacy) PrivateKey property and RSACryptoServiceProvider class.我个人不喜欢这种方法,因为它使用(现在是遗留的) PrivateKey 属性和 RSACryptoServiceProvider 类。 But, it has the advantage of working on all versions of .NET Framework (though not .NET Core on non-Windows systems, since RSACryptoServiceProvider is Windows-only).但是,它具有在所有版本的 .NET Framework 上工作的优势(尽管不是在非 Windows 系统上的 .NET Core,因为 RSACryptoServiceProvider 仅适用于 Windows)。

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);
}

If you already have cert.PrivateKey cast as an RSACryptoServiceProvider you can send it through UpgradeCsp.如果您已经将 cert.PrivateKey 转换为 RSACryptoServiceProvider,您可以通过 UpgradeCsp 发送它。 Since this is opening an existing key there'll be no extra material written to disk, it uses the same permissions as the existing key, and it does not require you to do an export.由于这是打开现有密钥,因此不会有额外的材料写入磁盘,它使用与现有密钥相同的权限,并且不需要您进行导出。

But (BEWARE!) do NOT set PersistKeyInCsp=false, because that will erase the original key when the clone is closed.但是(注意!)不要设置 PersistKeyInCsp=false,因为这会在克隆关闭时删除原始密钥。

If you run into this issue after upgrading to .Net 4.7.1 or above:如果您在升级到 .Net 4.7.1 或更高版本后遇到此问题:

.Net 4.7 and below: .Net 4.7 及以下:

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

.Net 4.7.1 and above: .Net 4.7.1 及更高版本:

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

Credits to Vladimir Kocjancic Vladimir Kocjancic 致谢

In dotnet core I had this:在 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();

which did not work.这不起作用。 Instead i used this相反,我使用了这个

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();

changed signature method => xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url更改签名方法 => xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM