在 C# 中执行 MD5 并一次下载的有效方法是什么?

What is an efficient way in C# of doing MD5 and download all at once?

我正在下载,然后进行 MD5 检查以确保下载成功。我有以下代码应该可以工作,但不是最有效的 - 特别是对于大文件。

        using (var client = new System.Net.WebClient())
        {
            client.DownloadFile(url, destinationFile);
        }

        var fileHash = GetMD5HashAsStringFromFile(destinationFile);
        var successful = expectedHash.Equals(fileHash, StringComparison.OrdinalIgnoreCase);

我担心的是字节全部流式传输到磁盘,然后 MD5 ComputeHash() 必须打开文件并再次读取所有字节。作为下载流的一部分,是否有一种好的、干净的方法来计算 MD5?理想情况下,MD5 应该作为某种副作用从 DownloadFile() 函数中掉出来。具有如下签名的函数:

string DownloadFileAndComputeHash(string url, string filename, HashTypeEnum hashType);

编辑:GetMD5HashAsStringFromFile()

添加代码
    public string GetMD5HashAsStringFromFile(string filename)
    {
        using (FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            var md5er = System.Security.Cryptography.MD5.Create();
            var md5HashBytes = md5er.ComputeHash(file);
            return BitConverter
                    .ToString(md5HashBytes)
                    .Replace("-", string.Empty)
                    .ToLower();
        }
    }

类似这样。

byte[] result;
using (var webClient = new System.Net.WebClient())
{
    result = webClient.DownloadData("http://some.url");
}

byte[] hash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(result);

如果您谈论的是大文件(我假设超过 1GB),您将希望以块的形式读取数据,然后通过 MD5 算法处理每个块,然后将其存储到磁盘。这是可行的,但我不知道有多少默认的 .NET 类 可以帮助你。

一种方法可能是使用自定义流包装器。首先,您从 WebClient 获得一个 Stream(通过 GetWebResponse() and then GetResponseStream()), then you wrap it, and then pass it to ComputeHash(stream)。当 MD5 在您的包装器上调用 Read() 时,包装器将在网络流上调用 Read,当收到,然后传回MD5。

我不知道如果你这样做会有什么问题等着你。

Is there a good, clean way of computing the MD5 as part of the download stream? Ideally, the MD5 should just fall out of the DownloadFile() function as a side effect of sorts.

您可以遵循此策略,进行 "chunked" 计算并最大程度地减少内存压力(和重复):

  1. 在 Web 客户端上打开响应流。
  2. 打开目标文件流。
  3. 在有可用数据时重复:
    • 从响应流中读取块到字节缓冲区
    • 写入目标文件流。
    • 使用TransformBlock方法将字节加入hash计算
  4. 使用TransformFinalBlock得到计算出的哈希码。

下面的示例代码显示了这是如何实现的。

public static byte[] DownloadAndGetHash(Uri file, string destFilePath, int bufferSize)
{
    using (var md5 = MD5.Create())
    using (var client = new System.Net.WebClient())
    {
        using (var src = client.OpenRead(file))
        using (var dest = File.Create(destFilePath, bufferSize))
        {
            md5.Initialize();
            var buffer = new byte[bufferSize];
            while (true)
            {
                var read = src.Read(buffer, 0, buffer.Length);
                if (read > 0)
                {
                    dest.Write(buffer, 0, read);
                    md5.TransformBlock(buffer, 0, read, null, 0);
                }
                else // reached the end.
                {
                    md5.TransformFinalBlock(buffer, 0, 0);
                    return md5.Hash;
                }
            }
        }
    }
}