带SHA256的SignedXml计算签名

时间:2015-03-12 09:22:55

标签: c# xml encryption digital-signature

我正在尝试使用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();处的错误。谁能告诉我我做错了什么?

4 个答案:

答案 0 :(得分:30)

X509Certificate2将私有密钥从pfx文件加载到 Microsoft增强加密提供程序v1.0 (提供程序类型1又名PROV_RSA_FULL)中,而不是&# 39; t支持SHA-256。

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

但是,另一个CryptoAPI提供程序, Microsoft增强型RSA和AES加密提供程序(提供程序类型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 :(得分:15)

导出和重新导入已经been given as an answer,但您还应该了解其他几个选项。

1。使用GetRSAPrivateKey和.NET 4.6.2(当前处于预览状态)

GetRSAPrivateKey(扩展名)方法返回"最佳可用类型"的RSA实例。对于密钥和平台(而不是PrivateKey属性,#34;每个人都知道"返回RSACryptoServiceProvider)。

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

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

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

我个人不喜欢这种方法,因为它使用(现在遗留的)PrivateKey属性和RSACryptoServiceProvider类。但是,它具有处理所有.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);
}

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

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

答案 2 :(得分:2)

如果升级到.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();

弗拉基米尔·科赞西奇

的积分

答案 3 :(得分:0)

在 dotnet core 中,我有这个:

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