能否使用缓冲读取计算MD5(或其他)哈希值?

35

我需要计算相当大的文件(几个GB)的校验和。可以使用以下方法完成:

    private byte[] calcHash(string file)
    {
        System.Security.Cryptography.HashAlgorithm ha = System.Security.Cryptography.MD5.Create();
        FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
        byte[] hash = ha.ComputeHash(fs);
        fs.Close();
        return hash;
    }

然而,通常使用缓冲方式(例如一次写入32MB)来写入文件。我确信曾经看到过哈希函数的重写,使我能够在写入时计算MD5(或其他)哈希值,即在计算一个缓冲区的哈希值后,将该结果哈希值输入下一次迭代。

大致如下伪代码:

byte [] hash = new byte [] { 0,0,0,0,0,0,0,0 };
while(!eof)
{
   buffer = readFromSourceFile();
   writefile(buffer);
   hash = calchash(buffer, hash);
}

哈希现在类似于在整个文件上运行calcHash函数所实现的内容。

现在,在.Net 3.5框架中我找不到任何类似的覆盖方法,我是在做梦吗?它从未存在过,还是我只是搜索得不好?同时进行写入和校验和计算的原因是由于文件太大,这样做很有意义。

5个回答

52
你可以使用TransformBlockTransformFinalBlock方法来分块处理数据。
// Init
MD5 md5 = MD5.Create();
int offset = 0;

// For each block:
offset += md5.TransformBlock(block, 0, block.Length, block, 0);

// For last block:
md5.TransformFinalBlock(block, 0, block.Length);

// Get the has code
byte[] hash = md5.Hash;

注意:这种方法可行(至少对于MD5提供程序),即将所有块发送到TransformBlock,然后发送一个空块到TransformFinalBlock以完成整个过程。


好的,但是加一分是因为您还提供了参考资料! - Adam Liss
1
哎呀,终于找到了!这就是我一直在寻找的函数。很高兴知道我没有自己编造出来。感谢Guffa和Rubens提供如此迅速的正确答案。给你们两个点赞,我会接受这个答案,因为它包含了代码示例。 - sindre j
5
请注意,在调用TransformBlock时,您可以等效地将第二个block实例替换为null;您实际上不希望发生任何复制;输出参数实际上与哈希处理无关。 - Eamon Nerbonne
2
同时,TransformFinalBlock可以将长度设为零。 - RandomInsano
1
是否可以转换前X个数据块,转储状态数据,然后在新的计算中恢复状态后继续下一个块?在云解决方案中有100GB文件,能够不必一次性处理整个文件将是很好的选择。机器可以进行回收等操作。 - Poul K. Sørensen
1
@pksorensen:我不这么认为,我没有看到任何获取或设置MD5对象计算状态的方法或属性。理论上当然是可能的,但您可能需要使用算法的单独实现,以便您可以添加处理状态的方法。 - Guffa

49

我喜欢上面的回答,但为了完整起见并且作为更普遍的解决方案,请参考 CryptoStream 类。如果您已经处理流,很容易将流包装在 CryptoStream 中,并将 HashAlgorithm 作为 ICryptoTransform 参数传递。

var file = new FileStream("foo.txt", FileMode.Open, FileAccess.Write);
var md5 = MD5.Create();
var cs = new CryptoStream(file, md5, CryptoStreamMode.Write);
while (notDoneYet)
{
    buffer = Get32MB();
    cs.Write(buffer, 0, buffer.Length);
}
System.Console.WriteLine(BitConverter.ToString(md5.Hash));
你可能需要在获取哈希值之前关闭流(以便HashAlgorithm知道已完成)。

你可能需要在获取哈希值之前关闭流(以便HashAlgorithm知道已完成)。


5

那个链接已经失效了,请尝试使用这个链接:http://www.infinitec.de/post/2007/06/09/Displaying-progress-updates-when-hashing-large-files.aspx - Cumbayah

3

我刚刚也需要做类似的事情,但想要异步读取文件。我使用了TransformBlock和TransformFinalBlock,并得到了与Azure一致的答案,因此我认为它是正确的!

private static async Task<string> CalculateMD5Async(string fullFileName)
{
  var block = ArrayPool<byte>.Shared.Rent(8192);
  try
  {
     using (var md5 = MD5.Create())
     {
         using (var stream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true))
         {
            int length;
            while ((length = await stream.ReadAsync(block, 0, block.Length).ConfigureAwait(false)) > 0)
            {
               md5.TransformBlock(block, 0, length, null, 0);
            }
            md5.TransformFinalBlock(block, 0, 0);
         }
         var hash = md5.Hash;
         return Convert.ToBase64String(hash);
      }
   }
   finally
   {
      ArrayPool<byte>.Shared.Return(block);
   }
}

什么是 ArrayPool - Shimmy Weitzhandler
好的,明白了:需要安装 System.Buffers 包才能使用 ArrayPool - Shimmy Weitzhandler
这很有用,但不是 .net 3.5 的解决方案。 - Khale_Kitha

3
哈希算法被期望能够处理此类情况,并且通常使用三个函数来实现: hash_init() - 被调用以分配资源并开始哈希。
hash_update() - 以新到达的数据为参数调用。
hash_final() - 完成计算并释放资源。
你可以查看http://www.openssl.org/docs/crypto/md5.html 或者 http://www.openssl.org/docs/crypto/sha.html 中的C语言标准示例,我相信在你的平台上也应该有类似的库。

回答不错,但问题中“在 .net 中它在哪里?”这部分仍然没有解决。 - Pascal Cuoq
@Pascal:请看以下两个很好的答案,它们都是在你的评论之前发布的。 - Adam Liss

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