在C#中生成XML文档哈希

11

在C#中,对XML文档进行哈希的最佳方法是什么?我想要对XML文档进行哈希,以便我可以知道它是否与生成时手动更改。 我不是出于安全考虑使用此功能——如果有人更改XML并更改哈希以匹配,则没有问题。

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

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

在您所期望的哈希过程中,空格符起了什么作用? - Colin Burnett
我对此持观望态度——一方面,我只关心数据,而不是格式。另一方面,识别任何更改可能有助于检查是否有人在文件中进行了操作。 - sourcenouveau
5个回答

10

.NET有实现XML数字签名规范。签名可以添加在原始XML文档内(即“封装的签名”),也可以单独存储/传输。

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


我喜欢这个解决方案,因为正如你所指出的那样,它已经被实现并成为了标准。 - sourcenouveau

6
您可以使用加密命名空间:
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的字符串表示形式来创建哈希。


1
请参阅 System.Security.Cryptography.MD5、System.Security.Cryptography.SHA1、System.Security.Cryptography.SHA256 等相关内容,并在此处查看比较:http://en.wikipedia.org/wiki/Cryptographic_hash_function。 - csharptest.net
2
Encoding.Default 是操作系统当前 ANSI 代码页的编码。因此,您的代码将根据区域和语言选项 - 高级选项中的设置而产生不同的结果。 - Wim Coenen
wcoenen 的观点非常公正。使用 Encoding.ASCII 或 Encoding.<某种一致的编码>。 - Matt Wrock

2

添加一个.NET引用到System.Security,并使用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;
}

1
我最近在工作中需要为部分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://dev59.com/eHRB5IYBdhLWcg3wcm2d#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));
    }
}

0
    public static string GetHashSHA1(this byte[] data)
    {
        using (var sha1 = new System.Security.Cryptography.SHA256CryptoServiceProvider())
        {
            return string.Concat(sha1.ComputeHash(data).Select(x => x.ToString("X2")));
        }
    }

读取文件字节后我使用的代码是 => System.IO.File.ReadAllBytes(filePath)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接