Генерация хэша XML-документа в C#

Каков наилучший способ хэширования XML-документа на С#? Я хотел бы хешировать документ XML, чтобы я мог определить, был ли он изменен вручную с момента его создания. Я не использую это для безопасности — это нормально, если кто-то изменит XML и изменит хеш, чтобы он соответствовал.

Например, я бы хешировал дочерние узлы корня и сохранял хэш как атрибут корня:

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

person M. Dudley    schedule 05.10.2009    source источник
comment
Как пробелы вступают в игру в желаемом хешировании?   -  person Colin Burnett    schedule 05.10.2009
comment
Я сомневаюсь в этом - с одной стороны, меня действительно интересуют только данные, а не форматирование. С другой стороны, идентификация каких-либо изменений может быть полезна для проверки того, не играл ли кто-то с файлом.   -  person M. Dudley    schedule 05.10.2009


Ответы (4)


В .NET есть классы, реализующие спецификация цифровой подписи XML. Подпись может быть добавлена ​​в исходный XML-документ (т. е. «подпись в оболочке») или сохранена/перенесена отдельно.

Это может быть немного излишним, поскольку вам не нужна безопасность, но у него есть то преимущество, что он уже реализован и является стандартом, который не зависит от языка или платформы.

person Wim Coenen    schedule 05.10.2009
comment
Мне нравится это решение, потому что, как вы указали, оно уже реализовано и является стандартом. - person M. Dudley; 18.11.2009

Вы можете использовать пространство имен криптографии:

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.

person Matt Wrock    schedule 05.10.2009
comment
см. также System.Security.Cryptography.MD5, System.Security.Cryptography.SHA1, System.Security.Cryptography.SHA256 и т. д. и просмотрите сравнение здесь: en.wikipedia.org/wiki/Cryptographic_hash_function - person csharptest.net; 05.10.2009
comment
Encoding.Default — это кодировка для текущей кодовой страницы ANSI операционной системы. Таким образом, ваш код будет давать разные результаты в зависимости от настроек на вкладке «Язык и региональные стандарты» — «Дополнительно». - person Wim Coenen; 06.10.2009
comment
wcoenen имеет очень справедливое замечание. Используйте Encoding.ASCII или Encoding.‹некоторая последовательная кодировка›. - person Matt Wrock; 06.10.2009

Добавьте ссылку .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;
}
person Gabe Halsmer    schedule 06.12.2013

Недавно мне пришлось реализовать контрольную сумму хэша для частичных 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 framework):

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));
    }
}
person KGC    schedule 13.09.2018