获取一个文件SHA256哈希代码和校验和

本文关键字:哈希 SHA256 代码 校验和 文件 一个 获取 | 更新日期: 2023-09-27 17:57:42

之前我问过一个关于组合SHA1+MD5的问题,但在那之后我明白了计算一个滞后文件的SHA1和MD5并没有比SHA256快多少。在我的例子中,一个4.6 GB的文件大约需要10分钟,在Linux系统中使用(C#MONO)的默认实现SHA256。

public static string GetChecksum(string file)
{
    using (FileStream stream = File.OpenRead(file))
    {
        var sha = new SHA256Managed();
        byte[] checksum = sha.ComputeHash(stream);
        return BitConverter.ToString(checksum).Replace("-", String.Empty);
    }
}

然后我读了这个主题,并以某种方式根据他们所说的内容更改了我的代码:

public static string GetChecksumBuffered(Stream stream)
{
    using (var bufferedStream = new BufferedStream(stream, 1024 * 32))
    {
        var sha = new SHA256Managed();
        byte[] checksum = sha.ComputeHash(bufferedStream);
        return BitConverter.ToString(checksum).Replace("-", String.Empty);
    }
}

但它没有这样的感情,大约需要9分钟。

然后我尝试在Linux中通过sha256sum命令测试我的文件,这需要大约28秒,上面的代码和Linux命令都给出了相同的结果!

有人建议我阅读哈希码和校验和之间的差异,我开始讨论这个主题来解释这些差异。

我的问题是:

  1. 是什么原因导致上面的代码和Linux sha256sum在时间上有如此不同?

  2. 上面的代码是做什么的?(我的意思是,它是散列码计算还是校验和计算?因为如果你在C#中搜索给定文件的散列码和文件的校验和,它们都会到达上面的代码。)

  3. 即使在SHA256具有抗碰撞能力的情况下,是否存在针对sha256sum的动机攻击?

  4. 如何使我的实现像C#中的sha256sum一样快?

获取一个文件SHA256哈希代码和校验和

public string SHA256CheckSum(string filePath)
{
    using (SHA256 SHA256 = SHA256Managed.Create())
    {
        using (FileStream fileStream = File.OpenRead(filePath))
            return Convert.ToBase64String(SHA256.ComputeHash(fileStream));
    }
}
  1. 我的最佳猜测是,在File.Read操作的Mono实现中有一些额外的缓冲。最近研究了一个大文件的校验和,在一台规格不错的Windows机器上,如果一切顺利运行,你应该预计每Gb大约有6秒。

    奇怪的是,在不止一次的基准测试中,SHA-512明显快于SHA-256(见下文3)。另一种可能性是,问题不在于分配数据,而在于一旦读取就处理字节。您可以在单个数组中使用TransformBlock(和TransformFinalBlock),而不是一口气读取流—我不知道这是否可行,但值得调查。

  2. 散列码和校验和之间的区别(几乎)在于语义。它们都计算出一个较短的"魔术"数,这个数对输入中的数据来说是相当独特的,尽管如果你有4.6GB的输入和64B的输出,"公平"就有些有限了。

    • 校验和是不安全的,只要做一点工作,你就可以从足够多的输出中找出输入,从一个输出向后工作到另一个输入,并做各种不安全的事情
    • 加密哈希需要更长的时间来计算,但只改变输入中的一位就会从根本上改变输出,而对于一个好的哈希(例如SHA-512),没有已知的方法可以从输出返回到输入
  3. MD5是可破解的:如果需要,您可以在PC上伪造输入以产生任何给定的输出。SHA-256(可能)仍然是安全的,但几年后不会出现;如果你的项目的使用寿命是几十年,那么假设你需要改变它。SHA-512没有已知的攻击,可能在很长一段时间内都不会,因为它比SHA-256更快,我还是推荐它。基准测试显示,计算SHA-512所需的时间大约是MD5的3倍,所以如果你的速度问题能够得到解决,那就是解决的方法。

  4. 不知道,除了上面提到的。你做得对。

要了解更多信息,请参阅Crypto.SE:SHA51比SHA256快?

编辑以回应评论中的问题

校验和的目的是允许您检查文件在最初写入和使用之间是否发生了变化。它通过生成一个小值(在SHA512的情况下为512位)来实现这一点,其中原始文件的每一位都至少对输出值有贡献。散列码的目的是一样的,只是其他人很难通过对文件进行精心管理的更改来获得相同的输出值。

前提是,如果校验和在开始时和检查时是相同的,那么文件也是相同的,如果它们不同,那么文件肯定已经改变了。上面所做的是通过一种算法对文件进行整体馈送,该算法对读取的位进行滚动、折叠和主轴旋转,以产生较小的值。

举个例子:在我目前正在编写的应用程序中,我需要知道任何大小的文件的部分是否发生了更改。我将文件拆分为16K个块,获取每个块的SHA-512哈希,并将其存储在另一个驱动器上的单独数据库中。当我查看文件是否发生了更改时,我会重现每个块的哈希,并将其与原始块进行比较。由于我使用的是SHA-512,更改后的文件具有相同哈希的可能性非常小,因此我可以自信地检测到100s GB数据的更改,同时只在数据库中存储几MB哈希。我在获取哈希的同时复制文件,并且这个过程完全是磁盘绑定的;将文件传输到USB驱动器大约需要5分钟,其中10秒可能与哈希有关。

缺少存储哈希的磁盘空间是我无法在帖子中解决的问题—买个U盘?

派对迟到了,但由于没有一个答案提到这一点,我想指出:

SHA256ManagedSystem.Security.Cryptography.HashAlgorithm类的实现,与读取操作相关的所有功能都在继承的代码中处理。

CCD_ 10使用固定的4096字节缓冲器从流中读取数据。因此,在这个调用中使用BufferedStream并不会有太大区别。

HashAlgorithm.ComputeHash(byte[])对整个字节数组进行操作,但它在每次调用后都会重置内部状态,因此它不能用于增量散列缓冲流。

您最好的选择是使用针对您的用例进行优化的第三方实现。

using (SHA256 SHA256 = SHA256Managed.Create())
            {
                using (FileStream fileStream = System.IO.File.OpenRead(filePath))
                {
                    string result = "";
                    foreach (var hash in SHA256.ComputeHash(fileStream))
                    {
                        result += hash.ToString("x2");
                    }
                    return result;
                }
            }

供参考:https://www.c-sharpcorner.com/article/how-to-convert-a-byte-array-to-a-string/

using System.Security.Cryptography;
using (var fileStream = System.IO.File.Create(filePath)){
  using (var sha = SHA256.Create())
  {
   var hash = Convert.ToBase64String(sha.ComputeHash(fileStream));
  }
}

试试这个,它对我有效,我也用PoweShell和另一个Python脚本仔细检查了哈希。(为奇怪的身份提前道歉)

using System;
using System.IO;
using System.Security.Cryptography;
    
public static string GetExecutableHash(string fullPathToFile)
            /* Returns HASH-256 of a given executable file. */
            {
                string hash = string.Empty;
    
                using (FileStream fileStream = new FileInfo(fullPathToFile).Open(FileMode.Open))
                {
                    try
                    {
                        fileStream.Position = 0;
                        byte[] hashValue = SHA256.Create().ComputeHash(fileStream);
                        hash = BitConverter.ToString(hashValue).Replace("-", String.Empty).ToLower();
                    }
                    catch (IOException e)
                    {
                        Console.WriteLine($"I/O Exception: {e.Message}");
                    }
                    catch (UnauthorizedAccessException e)
                    {
                        Console.WriteLine($"Access Exception: {e.Message}");
                    }
                }
                return hash;
            }