2

我目前面临一个问题,即必须对文件进行哈希处理,这会导致内存压力过大,我正在尝试找出我们是否可以使用文件流动态创建哈希。

在研究可能性时,我决定编写一个快速的小测试,并确保 MD5 的 ComputeHash 在采用字符串和流的方法调用之间返回相同的哈希值。

let CreateMD5HashFromString (value: string) =
     Convert.ToBase64String(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(value)))

let CreateMD5HashFromStream (value: Stream) =
     Convert.ToBase64String(MD5.Create().ComputeHash(value))

我正在使用以下单元测试来测试调用:

[<TestMethod>]
member this.``CreateMD5Hash is the same between a string and a file stream`` () =
    let sampleText = File.ReadAllText("Sample.txt")
    let textMD5 = Security.CreateMD5HashFromString(sampleText);
    let streamMD5 = Security.CreateMD5HashFromStream(File.OpenRead("Sample.txt"))

    Assert.AreEqual(textMD5, streamMD5)

它正在读取一个用于测试的小样本文件。此测试失败,因为生成的哈希值不同。对我来说,这似乎是不正确的,但并不完全确定。有谁知道这些是否应该相同?

另外,第二个问题,我是通过使用 ComputeHash 的流重载来节省自己的内存问题,还是在散列之前加载整个流?我试图分解相关的 .NET 程序集,但在试图追踪 HashCore 的幕后工作时迷失了方向。

4

2 回答 2

4

它实际上很简单:你不能假设文本等于它的底层二进制表示。

在这个创建读取示例文本为ASCII的示例中,它可以正常工作,就像您期望的那样:

public static void Main(string[] args)
{
    System.IO.File.WriteAllBytes("test", System.Text.Encoding.ASCII.GetBytes("test string"));

    var inputString = System.IO.File.ReadAllText("test");
    var inputBytes = System.IO.File.ReadAllBytes("test");
    var inputStream = new System.IO.FileStream("test", System.IO.FileMode.OpenOrCreate);

    var stringHash = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(System.Text.Encoding.ASCII.GetBytes(inputString)));
    var streamHash = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(inputStream));
    var bytesHash = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(inputBytes));

    Console.WriteLine("String hash: {0}", stringHash);
    Console.WriteLine("Stream hash: {0}", streamHash);
    Console.WriteLine("Bytes hash: {0}", streamHash);

    Console.WriteLine("\nMD5s {0}", stringHash == streamHash && streamHash == bytesHash ? "match" : "don't match");
}

带输出

String hash: b421md6Yb6t6IWJbeRZYnA==
Stream hash: b421md6Yb6t6IWJbeRZYnA==
Bytes hash: b421md6Yb6t6IWJbeRZYnA==

MD5s match

但是,这仅在磁盘上的文件是纯 ASCII 文件的情况下才有效。在任何其他情况下都是零保证。例如,许多非 ASCII 文件以 BOM(字节顺序标记)开头,以表示编码类型。这将在二进制字节数组散列中表示,而不是在内存中的字符串散列中表示。UTF-8 和 unicode 通常可以对同一个字符串有十几种不同的表示形式——当将字符串加载到string对象中时,字符串可以被规范化为与磁盘上不同的表示形式。

于 2013-01-08T00:59:44.927 回答
2

我认为关键问题是,源文件中使用了什么编码?

Encoding.ASCII.GetBytes如果您使用的字节数组包含与 相同的字节,则哈希值将相同Stream,但只有当您使用的文件包含与使用相同的编码并且GetBytes文件中没有签名时,才会出现这种情况。

这与 MD5 函数无关,因此您可以通过检查更轻松地进行测试(假设文件小于 10kB - 否则您需要更大的缓冲区):

let res1 = Encoding.ASCII.GetBytes(File.ReadAllText("test.txt"))
let buffer = Array.zeroCreate 10240
let size = File.OpenRead("D:\\temp\\test.fsx").Read(buf, 0, 10240)
let res2 = buffer.[0 .. size - 1]

res1 = res2 // Are the byte arrays the same?

当我尝试运行它时,我必须解决两件事:

  • 我使用的文件是用带有签名的 UTF-8 保存的,所以开头有 3 个字节指定编码(如果我使用,我只会得到相同的字节数组buffer.[3 .. size - 1]

  • 我必须使用相同的编码保存文件(在这种情况下为 ASCII,但一般来说,正确处理可能会很棘手)。或者,您可以在读取文件时指定编码,但您可能会散列无意义的文本。

于 2013-01-08T01:00:18.307 回答