Создание хеша для папки

Мне нужно создать хэш для папки, содержащей некоторые файлы. Я уже выполнил эту задачу для каждого из файлов, но я ищу способ создать один хэш для всех файлов в папке. Любые идеи, как это сделать?

(конечно, я могу создать хэш для каждого файла и связать его с каким-то большим хэшем, но это не способ, который мне нравится)

Спасибо заранее.

Ответ 1

Это хеширует все файлы (относительные) пути и содержимое и корректно обрабатывает порядок файлов.

И это быстро - как 30ms для каталога 4MB.

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

...

public static string CreateMd5ForFolder(string path)
{
    // assuming you want to include nested folders
    var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
                         .OrderBy(p => p).ToList();

    MD5 md5 = MD5.Create();

    for(int i = 0; i < files.Count; i++)
    {
        string file = files[i];

        // hash path
        string relativePath = file.Substring(path.Length + 1);
        byte[] pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower());
        md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);

        // hash contents
        byte[] contentBytes = File.ReadAllBytes(file);
        if (i == files.Count - 1)
            md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
        else
            md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0);
    }

    return BitConverter.ToString(md5.Hash).Replace("-", "").ToLower();
}

Ответ 2

Ответ Dunc работает хорошо; однако он не обрабатывает пустой каталог. Код ниже возвращает MD5 'd41d8cd98f00b204e9800998ecf8427e' (MD5 для потока длины длиной 0) для пустого каталога.

public static string CreateDirectoryMd5(string srcPath)
{
    var filePaths = Directory.GetFiles(srcPath, "*", SearchOption.AllDirectories).OrderBy(p => p).ToArray();

    using (var md5 = MD5.Create())
    {
        foreach (var filePath in filePaths)
        {
            // hash path
            byte[] pathBytes = Encoding.UTF8.GetBytes(filePath);
            md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);

            // hash contents
            byte[] contentBytes = File.ReadAllBytes(filePath);

            md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0);
        }

        //Handles empty filePaths case
        md5.TransformFinalBlock(new byte[0], 0, 0);

        return BitConverter.ToString(md5.Hash).Replace("-", "").ToLower();
    }
}

Ответ 3

Создайте tarball файлов, хэш-архив.

> tar cf hashes *.abc
> md5sum hashes

Или хешировать отдельные файлы и выходные данные в хэш-команду.

> md5sum *.abc | md5sum

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

Ответ 4

Объединить имена файлов и файлов в одну большую строку и хэш, или сделать хэширование в кусках для производительности.

Конечно, вам нужно учитывать несколько вещей:

  • Вам нужно отсортировать файлы по имени, поэтому вы не получите два разных хэша в случае изменения порядка файлов.
  • Используя этот метод, вы учитываете только имена файлов и контент. если имя файла не засчитывается, вы можете сортировать по контенту сначала, а затем хеш, если имеет значение больше атрибутов (ctime/mtime/hidden/archived..), включите их в строку, которая будет хеширована.

Ответ 5

Если у вас уже есть хеши для всех файлов, просто сортируйте хеши в алфавитном порядке, объедините их и снова хэш их, чтобы создать хэш uber.

Ответ 6

Здесь решение, которое использует потоковую передачу, чтобы избежать проблем с памятью и задержками.

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

Наконец, это решение позволяет вам контролировать алгоритм хэширования и какие файлы хешируются и в каком порядке.

public static class HashAlgorithmExtensions
{
    public static async Task<byte[]> ComputeHashAsync(this HashAlgorithm alg, IEnumerable<FileInfo> files, bool includePaths = true)
    {
        using (var cs = new CryptoStream(Stream.Null, alg, CryptoStreamMode.Write))
        {
            foreach (var file in files)
            {
                if (includePaths)
                {
                    var pathBytes = Encoding.UTF8.GetBytes(file.FullName);
                    cs.Write(pathBytes, 0, pathBytes.Length);
                }

                using (var fs = file.OpenRead())
                    await fs.CopyToAsync(cs);
            }

            cs.FlushFinalBlock();
        }

        return alg.Hash;
    }
}

Пример, который хэширует все файлы в папке:

async Task<byte[]> HashFolder(DirectoryInfo folder, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
    using(var alg = MD5.Create())
        return await alg.ComputeHashAsync(folder.EnumerateFiles(searchPattern, searchOption));
}