在C#中生成XML文档哈希

时间:2009-10-05 17:04:29

标签: c# xml hash canonicalization

在C#中散列XML文档的最佳方法是什么?我想散列一个XML文档,以便我可以判断它是否从生成时手动更改。我没有使用它来保证安全性 - 如果有人更改了XML,并且更改了哈希以匹配,那就没关系。

例如,我会对根的子节点进行哈希并将哈希存储为根的属性:

<RootNode Hash="abc123">
    <!-- Content to hash here -->
</RootNode>

4 个答案:

答案 0 :(得分:10)

.NET有classes来实现XML digital signature spec。签名可以添加到原始XML文档中(即“封装签名”),也可以单独存储/传输。

由于您不需要安全性,因此它可能有点过分,但它具有已经实现的优点,并且是不依赖于语言或平台的标准。

答案 1 :(得分:5)

您可以使用加密名称空间:

System.Security.Cryptography.MACTripleDES hash = new System.Security.Cryptography.MACTripleDES(Encoding.Default.GetBytes("mykey"));
string hashString = Convert.ToBase64String(hash.ComputeHash(Encoding.Default.GetBytes(myXMLString)));

您只需使用密钥创建哈希密码学家,然后使用xml的字符串reqpresentation创建哈希。

答案 2 :(得分:2)

向System.Security添加.NET引用,并使用XmlDsigC14NTransform。这是一个例子......

/* http://www.w3.org/TR/xml-c14n

    Of course is cannot detect these are the same...

       <color>black</color>    vs.   <color>rgb(0,0,0)</color>

    ...because that's dependent on app logic's interpretation of XML data.

    But otherwise it gets the following right...
    •Normalization of whitespace in start and end tags
    •Lexicographic ordering of namespace and attribute
    •Empty element conversion to start-end tag pair 
    •Retain all whitespace between tags

    And more.
 */
public static string XmlHash(XmlDocument myDoc)
{
    var t = new System.Security.Cryptography.Xml.XmlDsigC14NTransform();
    t.LoadInput(myDoc);
    var s = (Stream)t.GetOutput(typeof(Stream));
    var sha1 = SHA1.Create();

    var hash = sha1.ComputeHash(s);
    var base64String = Convert.ToBase64String(hash);
    s.Close();
    return base64String;
}

答案 3 :(得分:0)

最近,我不得不为工作中的部分XML文档实现哈希“校验和”(我们使用XElement)。初步的性能测试表明,使用查找表创建十六进制字符串哈希值时,与不使用该表时相比,我的计算机上运行时的速度提高了约3倍。

这是我的实现方式:

using System.Xml.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Linq;

/// <summary>
/// Provides a way to easily compute SHA256 hash strings for XML objects.
/// </summary>
public static class XMLHashUtils
{
    /// <summary>
    /// Precompute a hexadecimal lookup table for runtime performance gain, at the cost of memory and startup performance loss.
    /// SOURCE: https://stackoverflow.com/a/18574846
    /// </summary>
    static readonly string[] hexLookupTable = Enumerable.Range(0, 256).Select(integer => integer.ToString("x2")).ToArray();

    static readonly SHA256Managed sha256 = new SHA256Managed();

    /// <summary>
    /// Computes a SHA256 hash string from an XElement and its children.
    /// </summary>
    public static string Hash(XElement xml)
    {
        string xmlString = xml.ToString(SaveOptions.DisableFormatting); // Outputs XML as single line
        return Hash(xmlString);
    }

    /// <summary>
    /// Computes a SHA256 hash string from a string.
    /// </summary>
    static string Hash(string stringValue)
    {
        byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(stringValue));
        return BytesToHexString(hashBytes);
    }

    /// <summary>
    /// Converts a byte array to a hexadecimal string using a lookup table.
    /// </summary>
    static string BytesToHexString(byte[] bytes)
    {
        int length = bytes.Length;
        StringBuilder sb = new StringBuilder(length * 2); // Capacity fits hash string length
        for (var i = 0; i < length; i++)
        {
            sb.Append(hexLookupTable[bytes[i]]); // Using lookup table for faster runtime conversion
        }
        return sb.ToString();
    }
}

下面是使用NUnit框架进行的一些单元测试:

using NUnit.Framework;
using System.Linq;
using System.Xml.Linq;

public class XMLHashUtilsTest
{
    /// <summary>
    /// Outputs XML: <root><child attribute="value" /></root>
    /// where <child /> node repeats according to childCount
    /// </summary>
    XElement CreateXML(int childCount)
    {
        return new XElement("root", Enumerable.Repeat(new XElement("child", new XAttribute("attribute", "value")), childCount));
    }

    [Test]
    public void HashIsDeterministic([Values(0,1,10)] int childCount)
    {
        var xml = CreateXML(childCount);
        Assert.AreEqual(XMLHashUtils.Hash(xml), XMLHashUtils.Hash(xml));
    }

    [Test]
    public void HashChanges_WhenChildrenAreDifferent([Values(0,1,10)] int childCount)
    {
        var xml1 = CreateXML(childCount);
        var xml2 = CreateXML(childCount + 1);
        Assert.AreNotEqual(XMLHashUtils.Hash(xml1), XMLHashUtils.Hash(xml2));
    }

    [Test]
    public void HashChanges_WhenRootNameIsDifferent([Values("A","B","C")]string nameSuffix)
    {
        var xml1 = CreateXML(1);
        var xml2 = CreateXML(1);
        xml2.Name = xml2.Name + nameSuffix;
        Assert.AreNotEqual(XMLHashUtils.Hash(xml1), XMLHashUtils.Hash(xml2));
    }

    [Test]
    public void HashChanges_WhenRootAttributesAreDifferent([Values("A","B","C")]string attributeName)
    {
        var xml1 = CreateXML(1);
        var xml2 = CreateXML(1);
        xml2.Add(new XAttribute(attributeName, "value"));
        Assert.AreNotEqual(XMLHashUtils.Hash(xml1), XMLHashUtils.Hash(xml2));
    }
}