9

有没有办法用 RSA 对 XML 文件进行签名并使用命名空间前缀“ds:Signature”而不是“Signature”?我花了很多时间试图解决这个问题,据我所知,没有解决方案。

似乎它在 System.Security.Cryptography.Xml.Signature 类中是硬编码的。

XmlElement element = document.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");

如果有人知道解决方案,我需要像这样签名它,因为导入它的软件会使用“ds:signature”验证它,因此使用“ds”前缀的软件会像这样验证它:

    public static bool VerifySignature(XmlDocument doc, RSA key, string prefix)
    {
        SignedXml xml = new SignedXml(doc);
        string str = "Signature";
        if (!string.IsNullOrEmpty(prefix))
        {
            str = string.Format("{0}:{1}", prefix, str);
        }
        XmlNodeList elementsByTagName = doc.GetElementsByTagName(str);
        xml.LoadXml((XmlElement)elementsByTagName[0]);
        return xml.CheckSignature(key);
    }

  VerifySignature(xmlDoc, rsa, "ds");

通常它的标志是这样的:

<kk>blabla<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>rVL2nKjPTBhL9IDHYpu69OiE8gI=</DigestValue></Reference></SignedInfo><SignatureValue>CfXW9D/ErmHjzxIjy0/54/V3nst6j/XXcu7keR17LApfOZEpxjEvAlG3VnBZIi3jxQzU6t9RkmfDyngcRZccJByuuA6YDwFTQxZNRgu2GRoZxMKWnkm+MtQ0jH0Fo78GivCxV+iIewZvsrUQLzG01cXuZSH/k2eeMUaEooJaLQiYpO2aNVn5xbosTPtGlsACzFWz34E69/ZeeLZbXLc3jpDO+opxdYJ5e+Tnk/UM2Klt+N+m7Gh/sUNTPgkDiwP3q3y3O9tvCT0G2XmQaWBP4rw9TIoYHQtucm2b8R2JeggbeRKOetbRYV218RT8CK2Yuy0FIUlQXdabKyp9F96Yc55g8eNe10FGtgietH2iqquIVFLCA8fu3SZNLDPMoyHnVNKdBvI35+S8hrAaybEkMvo7iYnUSY5KrlGSfGGtfQXdaISutAzcnGPDFXgZXPNzNy7eL0u+Lt3yWWkj7wh6Zeh4fH2+nXDWYCWbLpegAEX4ZWSI5Ts6D1TplMJTGH1F0GyflehH4u+W4Lc3TvkB4dWjEuiKgnpl3hcvoj2CWFaeAxXMd/64tU/YMm8+1gSBjkVH6oV+QlI/m0z6M8FPVEVC2as0wLG2woVwmzVLcaQKyPi7NN4eO9ea7QNfaRHaofU4LQO/Y3FNJOP+uMfYlGJKWSr3qv29+BQjeNldNJY=</SignatureValue></Signature></kk>

我需要它这样做:

<kk>blabla<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><ds:DigestValue>rVL2nKjPTBhL9IDHYpu69OiE8gI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>CfXW9D/ErmHjzxIjy0/54/V3nst6j/XXcu7keR17LApfOZEpxjEvAlG3VnBZIi3jxQzU6t9RkmfDyngcRZccJByuuA6YDwFTQxZNRgu2GRoZxMKWnkm+MtQ0jH0Fo78GivCxV+iIewZvsrUQLzG01cXuZSH/k2eeMUaEooJaLQiYpO2aNVn5xbosTPtGlsACzFWz34E69/ZeeLZbXLc3jpDO+opxdYJ5e+Tnk/UM2Klt+N+m7Gh/sUNTPgkDiwP3q3y3O9tvCT0G2XmQaWBP4rw9TIoYHQtucm2b8R2JeggbeRKOetbRYV218RT8CK2Yuy0FIUlQXdabKyp9F96Yc55g8eNe10FGtgietH2iqquIVFLCA8fu3SZNLDPMoyHnVNKdBvI35+S8hrAaybEkMvo7iYnUSY5KrlGSfGGtfQXdaISutAzcnGPDFXgZXPNzNy7eL0u+Lt3yWWkj7wh6Zeh4fH2+nXDWYCWbLpegAEX4ZWSI5Ts6D1TplMJTGH1F0GyflehH4u+W4Lc3TvkB4dWjEuiKgnpl3hcvoj2CWFaeAxXMd/64tU/YMm8+1gSBjkVH6oV+QlI/m0z6M8FPVEVC2as0wLG2woVwmzVLcaQKyPi7NN4eO9ea7QNfaRHaofU4LQO/Y3FNJOP+uMfYlGJKWSr3qv29+BQjeNldNJY=</ds:SignatureValue></ds:Signature></kk>
4

6 回答 6

8

如果有人知道解决方案,我需要这样签名,因为导入它的软件会使用 "ds:signature" 验证它,所以使用 "ds" 前缀

前缀应该不重要——重要的是元素所在的命名空间。命名空间的表达方式无关紧要。如果是这样,那表明验证代码存在缺陷,我会说。

但是,如果您真的想这样做,是否有任何理由不想将元素替换为具有相同内容的元素,而是使用您想要的前缀?在 LINQ to XML 中做到这一点应该不难。

于 2012-08-31T16:40:16.747 回答
8

我在这里找到了解决方案

using System;
using System.Reflection;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace mysign
{
public class PrefixedSignedXML : SignedXml
{
    public PrefixedSignedXML(XmlDocument document)
        : base(document)
    { }

    public PrefixedSignedXML(XmlElement element)
        : base(element)
    { }

    public PrefixedSignedXML()
        : base()
    { }

    public void ComputeSignature(string prefix)
    {
        this.BuildDigestedReferences();
        AsymmetricAlgorithm signingKey = this.SigningKey;
        if (signingKey == null)
        {
            throw new CryptographicException("Cryptography_Xml_LoadKeyFailed");
        }
        if (this.SignedInfo.SignatureMethod == null)
        {
            if (!(signingKey is DSA))
            {
                if (!(signingKey is RSA))
                {
                    throw new CryptographicException("Cryptography_Xml_CreatedKeyFailed");
                }
                if (this.SignedInfo.SignatureMethod == null)
                {
                    this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
                }
            }
            else
            {
                this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
            }
        }
        SignatureDescription description = CryptoConfig.CreateFromName(this.SignedInfo.SignatureMethod) as SignatureDescription;
        if (description == null)
        {
            throw new CryptographicException("Cryptography_Xml_SignatureDescriptionNotCreated");
        }
        HashAlgorithm hash = description.CreateDigest();
        if (hash == null)
        {
            throw new CryptographicException("Cryptography_Xml_CreateHashAlgorithmFailed");
        }
        this.GetC14NDigest(hash, prefix);
        this.m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
    }

    public XmlElement GetXml(string prefix)
    {
        XmlElement e = this.GetXml();
        SetPrefix(prefix, e);
        return e;
    }

    //Invocar por reflexión al método privado SignedXml.BuildDigestedReferences
    private void BuildDigestedReferences()
    {
        Type t = typeof(SignedXml);
        MethodInfo m = t.GetMethod("BuildDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance);
        m.Invoke(this, new object[] { });
    }

    private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
    {
        //string securityUrl = (this.m_containingDocument == null) ? null : this.m_containingDocument.BaseURI;
        //XmlResolver xmlResolver = new XmlSecureResolver(new XmlUrlResolver(), securityUrl);
        XmlDocument document = new XmlDocument();
        document.PreserveWhitespace = true;
        XmlElement e = this.SignedInfo.GetXml();
        document.AppendChild(document.ImportNode(e, true));
        //CanonicalXmlNodeList namespaces = (this.m_context == null) ? null : Utils.GetPropagatedAttributes(this.m_context);
        //Utils.AddNamespaces(document.DocumentElement, namespaces);

        Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;
        //canonicalizationMethodObject.Resolver = xmlResolver;
        //canonicalizationMethodObject.BaseURI = securityUrl;
        SetPrefix(prefix, document.DocumentElement); //establecemos el prefijo antes de se que calcule el hash (o de lo contrario la firma no será válida)
        canonicalizationMethodObject.LoadInput(document);
        return canonicalizationMethodObject.GetDigestedOutput(hash);
    }

    private void SetPrefix(string prefix, XmlNode node)
    {
        foreach (XmlNode n in node.ChildNodes)
            SetPrefix(prefix, n);
        node.Prefix = prefix;
    }
}
}
于 2012-09-09T21:46:31.503 回答
1

我尝试了这些解决方案,但没有奏效。但是,通过查看 .NET 源代码 ( http://referencesource.microsoft.com/ ),我们可以看到,通过向 SignedXml 提供派生的 XmlDocument 类(可以在其中添加命名空间),可以轻松实现这一点。但是,在“SignedInfo”和后代中具有“ds”前缀将导致签名失败。在不破坏签名的情况下,这是我能做的最好的事情:


XmlDsigDocument.cs

using System;
using System.Collections.Generic;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;

namespace CustomSecurity
{
class XmlDsigDocument : XmlDocument
{
    // Constants
    public const string XmlDsigNamespacePrefix = "ds";

    /// <summary>
    /// Override CreateElement function as it is extensively used by SignedXml
    /// </summary>
    /// <param name="prefix"></param>
    /// <param name="localName"></param>
    /// <param name="namespaceURI"></param>
    /// <returns></returns>
    public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
    {
        // CAntonio. If this is a Digital signature security element, add the prefix. 
        if (string.IsNullOrEmpty(prefix))
        {
            // !!! Note: If you comment this line, you'll get a valid signed file! (but without ds prefix)
            // !!! Note: If you uncomment this line, you'll get an invalid signed file! (with ds prefix within 'Signature' object)
            //prefix = GetPrefix(namespaceURI);

            // The only way to get a valid signed file is to prevent 'Prefix' on 'SignedInfo' and descendants.
            List<string> SignedInfoAndDescendants = new List<string>();
            SignedInfoAndDescendants.Add("SignedInfo");
            SignedInfoAndDescendants.Add("CanonicalizationMethod");
            SignedInfoAndDescendants.Add("InclusiveNamespaces");
            SignedInfoAndDescendants.Add("SignatureMethod");
            SignedInfoAndDescendants.Add("Reference");
            SignedInfoAndDescendants.Add("Transforms");
            SignedInfoAndDescendants.Add("Transform");
            SignedInfoAndDescendants.Add("InclusiveNamespaces");
            SignedInfoAndDescendants.Add("DigestMethod");
            SignedInfoAndDescendants.Add("DigestValue");
            if (!SignedInfoAndDescendants.Contains(localName))
            {
                prefix = GetPrefix(namespaceURI);
            }
        }

        return base.CreateElement(prefix, localName, namespaceURI);
    }

    /// <summary>
    /// Select the standar prefix for the namespaceURI provided
    /// </summary>
    /// <param name="namespaceURI"></param>
    /// <returns></returns>
    public static string GetPrefix(string namespaceURI)
    {
        if (namespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
            return "ec";
        else if (namespaceURI == SignedXml.XmlDsigNamespaceUrl)
            return "ds";

        return string.Empty;
    }
}
}

这用于 SignedXml 创建:

    // Create a new XML document.
    XmlDsigDocument doc = new XmlDsigDocument();

    // Load the passed XML file using its name.
    doc.Load(new XmlTextReader(FileName));

    // Create a SignedXml object.
    SignedXml signedXml = new SignedXml(doc);

您可以在以下位置查看完整的源文件:

https://social.msdn.microsoft.com/Forums/en-US/cd595379-f66a-49c8-8ca2-62acdc58b252/add-prefixds-signedxml?forum=xmlandnetfx

于 2016-07-14T11:23:16.170 回答
0

可能正确的 SetPrefix 代码如下所示:

    private void SetPrefix(String prefix, XmlNode node) {
        foreach (XmlNode n in node.ChildNodes)
        {
            SetPrefix(prefix, n);
            n.Prefix = prefix;
        }
    }
于 2013-02-14T12:20:39.073 回答
0

George Dima 提供的代码有效。

我将解释它是如何工作的。

当您调用 ComputeSignature 方法时,这将通过消化 SignedInfo 节点的值来生成签名值。

George Dima 提供的代码在获取摘要值之前将前缀添加到 SignedInfo 节点及其子节点。这不会将前缀添加到整个 xml 结构中

这是生成签名信息节点摘要值的方法

private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
{        
    XmlDocument document = new XmlDocument();
    document.PreserveWhitespace = true;
    XmlElement e = this.SignedInfo.GetXml();
    document.AppendChild(document.ImportNode(e, true));        
    Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;

    SetPrefix(prefix, document.DocumentElement); //HERE'S WHERE THE PREFIX IS ADDED TO GET THE DIGEST VALUE
    canonicalizationMethodObject.LoadInput(document);
    return canonicalizationMethodObject.GetDigestedOutput(hash);
}

所以你现在有了带前缀的 SignedInfo 节点的摘要值,这个值将用于获取签名值,但你仍然没有带前缀的 xml,所以如果你这样做

signedXml.GetXml();

您将获得不带前缀的 xml,当然因为签名值是考虑到 ds 前缀来计算的,所以您将有一个无效的签名,所以您要做的就是调用 GetXml,将前缀的值传递给它,在这种情况下为“ds “ 像这样

signedXml.GetXml("ds");
于 2017-06-08T16:51:41.893 回答
0

我同意前缀不应该很重要,但是......

如果使用 XPath,XML 在 C# 中会变得更容易:

var s = signedXml.GetXml();
XmlNodeList nodes = s.SelectNodes("descendant-or-self::*");
foreach (XmlNode childNode in nodes)
{
    childNode.Prefix = "dsig";
}
于 2017-01-10T21:51:40.687 回答