【问题标题】:How do you create the hash of a folder in C#?如何在 C# 中创建文件夹的哈希?
【发布时间】:2021-05-16 14:31:30
【问题描述】:

我需要为包含一些文件的文件夹创建哈希。我已经为每个文件完成了这项任务,但我正在寻找一种方法来为文件夹中的所有文件创建一个哈希。关于如何做到这一点的任何想法?

(当然我可以为每个文件创建哈希并将其连接到一些大哈希,但这不是我喜欢的方式)

【问题讨论】:

    标签: c# security


    【解决方案1】:

    这会散列所有文件(相对)路径和内容,并正确处理文件排序。

    而且速度很快——对于一个 4MB 的目录来说需要 30 毫秒。

    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();
    }
    

    【讨论】:

    • 如果您曾经将其部署到具有强制 FIPS 合规性的本地安全策略的服务器上,请注意 FIPS 合规性
    • @SkeetJon 该技术对于任何加密算法都是相同的,因此您可以将 SHA 替换为 FIPS 机器。
    • 小心搜索模式 - 应该是“*”而不是“*.*”
    • File.ReadAllBytes 可以证明这种方法非常占用内存。
    • 在你的方法中,如果文件系统以不同的顺序返回相同的文件,我们将得到不同的哈希值。
    【解决方案2】:

    邓克的回答很好;但是,它不处理空目录。下面的代码返回空目录的 MD5 'd41d8cd98f00b204e9800998ecf8427e'(长度为 0 的字符流的 MD5)。

    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();
        }
    }
    

    【讨论】:

    • 如果你使用这个版本,你会想要将filePath截断为一个相对路径来创建pathBytes
    【解决方案3】:

    创建文件的压缩包,散列压缩包。

    > tar cf hashes *.abc
    > md5sum hashes

    或者散列单个文件并将输出传递到散列命令中。

    &gt; md5sum *.abc | md5sum

    编辑:上述两种方法都不会对文件进行排序,因此每次调用可能会返回不同的哈希值,具体取决于 shell 扩展星号的方式。

    【讨论】:

    • 这是唯一考虑到所有元信息的答案,如日期、访问权限、uid、guid、...
    【解决方案4】:

    这是一个使用流式传输来避免内存和延迟问题的解决方案。

    默认情况下,文件路径包含在哈希中,这不仅会考虑文件中的数据,还会考虑文件系统条目本身,从而避免哈希冲突。这篇文章被标记为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));
    }
    

    【讨论】:

    • 此外,我建议对文件名进行排序并使用Path.GetRelativePath(folder, file.FullName),这样即使文件位于不同的文件夹位置,哈希值也很稳定。
    • 同意files 可用于在散列之前进行排序。但是,Path.GetRelativePath 只能在 netcore 中找到。对文件的完整路径进行排序应该可以解决问题。对于具有许多条目的目录,应特别注意排序不会太昂贵。有一些方法可以生成文件的确定性枚举,而无需对整个序列进行排序。
    • 请注意,NET Framework 4.5 中引入了 FileStream.CopyToAsync()
    【解决方案5】:

    如果您已经拥有所有文件的哈希值,只需按字母顺序对哈希值进行排序,将它们连接起来并再次对其进行哈希处理以创建超级哈希值。

    【讨论】:

    • 这样做的问题是您可以更改文件名并仍然获得相同的哈希值 - 也许是您想要的,也许不是
    【解决方案6】:

    将文件名和文件内容连接到一个大字符串中并对其进行哈希处理,或者以块的形式进行哈希处理以提高性能。

    当然你需要考虑一些事情:

    • 您需要按名称对文件进行排序,这样在文件顺序发生变化时不会得到两个不同的哈希值。
    • 使用此方法,您只需考虑文件名和内容。如果文件名不计算在内,您可以先按内容排序然后散列,如果更多属性(ctime/mtime/hidden/archived..)很重要,请将它们包含在待散列字符串中。

    【讨论】:

    • 感谢您的回复。字符串可能真的很大,所以我需要将它分成块,只是想着如何正确地做到这一点。
    • 我记得 C# 哈希器有一个函数可以为它们提供块,最后你可以要求获得最终的哈希,不确定这些函数/类是什么。有了它们,您可以按照您喜欢的方式在内存中对输入进行排序,然后循环文件并将块加载到几百 KB 并将其提供给哈希器,这样您不需要太多内存,但仍然需要一些时间进行哈希处理,这是你无法摆脱的。
    • 这可能会导致内存相关问题。
    【解决方案7】:

    快速而肮脏的文件夹哈希,不会下到子订单或读取二进制数据。它基于文件和子文件夹名称。

    Public Function GetFolderHash(ByVal sFolder As String) As String
        Dim oFiles As List(Of String) = IO.Directory.GetFiles(sFolder).OrderBy(Function(x) x.Count).ToList()
        Dim oFolders As List(Of String) = IO.Directory.GetDirectories(sFolder).OrderBy(Function(x) x.Count).ToList()
        oFiles.AddRange(oFolders)
    
        If oFiles.Count = 0 Then
            Return ""
        End If
    
        Dim oDM5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create()
        For i As Integer = 0 To oFiles.Count - 1
            Dim sFile As String = oFiles(i)
            Dim sRelativePath As String = sFile.Substring(sFolder.Length + 1)
            Dim oPathBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(sRelativePath.ToLower())
    
            If i = oFiles.Count - 1 Then
                oDM5.TransformFinalBlock(oPathBytes, 0, oPathBytes.Length)
            Else
                oDM5.TransformBlock(oPathBytes, 0, oPathBytes.Length, oPathBytes, 0)
            End If
        Next
    
        Return BitConverter.ToString(oDM5.Hash).Replace("-", "").ToLower()
    End Function
    

    【讨论】:

      猜你喜欢
      • 2011-09-20
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 2011-04-20
      • 2012-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多