在C#中生成运行哈希(或校验和)?

5

前言:

我正在进行一个数据导入,其中包含验证提交阶段。其想法是:第一阶段允许从各种来源获取数据,然后在数据库上运行各种插入/更新/验证操作。提交将被回滚,但会生成“验证哈希/校验和”。提交阶段相同,但如果“验证哈希/校验和”相同,则操作将被提交。(数据库将在适当的隔离级别下运行。)

限制条件:

  • 输入读取和操作仅向前读取一次
  • 不想预先创建流(例如,写入MemoryStream不可取),因为可能有大量数据。(它可以在我们的服务器/负载上工作,但假装内存有限。)
  • 不想“自己创建”。(我知道有可用的代码,例如Damien的CRC-32,我可以使用/修改,但更喜欢一些“标准”的东西。)

我(认为我)正在寻找的内容:

一种基于输入+操作生成哈希值(例如SHA1或MD5?)或校验和(例如CRC32但希望更多)的方法。 (输入/操作本身可以被散列为更适合校验和生成的值,但仅“写入流”会很好。)

所以,问题是:

如何在C#中生成运行哈希(或校验和)?

同时,虽然有可修改为运行操作的CRC32实现,但SHAx或MD5哈希的运行如何呢?

我是否错过了某种方便的流方法,可以用作适配器?

(批评是受欢迎的,但请回答上述问题。此外,我宁愿不处理线程。;-)


你如何在不使用缓冲的情况下运行两次导入并同时仅读取一次输入? - usr
@usr 我的意思是每次运行只读取一次。也就是说,不会首先在输入流上运行哈希函数。 - user166390
好的,"running hash" 对你有哪些属性? - usr
最好是可以像流一样使用的(我不断地推入数据,等到所有数据都写入后才得到结果)算法。实际的“强度”要求有些模糊,但CRC32可能已经足够了,但是...那就没那么有趣了 :-) - user166390
@pst,仅供我自己学习,验证/提交是用于什么(解决了什么问题)?为什么要做两次相同的事情,但只在第二次运行时提交? - Andrew Savinykh
@zespri 在我的情况下,是因为我让用户(他们是“管理员”,但对我来说是用户)批量导入数据。第一步类似于“假设”,允许查看将添加/更新的记录以及任何数据警告消息。通常,如果只是同步运行,就会有一个巨大的事务/提交(在命令行版本中确实如此),但由于这是异步发生的(文件上传/导入运行/日志审核/提交),所以在其中可能会发生一些事情 - 不同的导入,更改文件等。检查是为了“您所看到的”(关于日志)就是“您所得到的”。 - user166390
4个回答

9
您可以多次调用HashAlgorithm.TransformBlock,然后调用TransformFinalBlock将会给您所有块的结果。
通过从流中读取x个字节来分块输入,并对每个块调用TransformBlock
编辑(来自msdn示例):
public static void PrintHashMultiBlock(byte[] input, int size)
{
    SHA256Managed sha = new SHA256Managed();
    int offset = 0;

    while (input.Length - offset >= size)
        offset += sha.TransformBlock(input, offset, size, input, offset);

    sha.TransformFinalBlock(input, offset, input.Length - offset);
    Console.WriteLine("MultiBlock {0:00}: {1}", size, BytesToStr(sha.Hash));
}

抱歉,我没有现成的例子可供参考,但是对于您来说,您基本上是用自己的块替换 input,然后 size 将是该块中的字节数。您需要自行跟踪偏移量。


1
你能发一份代码示例和解释吗?我认为这是正确的方法,但是很难理解它是如何工作的。 - Kendall Frey
谢谢您提供的示例,现在我可以理解了(+1)。 - user166390

5
哈希函数有构建和最终化两个阶段。在构建阶段,您可以将任意数量的数据推入其中。数据可以按您喜欢的方式拆分。最后,完成哈希操作并获得哈希值。
您可以使用可写的CryptoStream来编写数据。这是最简单的方法。

这个名字听起来像是朝着正确的方向发展。有文档的链接吗?(我已经在查看它,但答案应该包含这个信息;-)) - user166390
@pst 我认为它是HashCore和HashFinal。请参见http://msdn.microsoft.com/en-us/library/system.security.cryptography.hashalgorithm.hashcore(v=vs.90)和http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5cryptoserviceprovider.hashfinal(v=vs.90)。 - Chris Shain

2
您可以使用MD5CryptoServiceProviderComputeHash方法生成MD5哈希值。该方法需要一个流作为输入。
创建一个内存或文件流,将哈希值的输入写入其中,完成后调用ComputeHash方法即可。
var myStream = new MemoryStream();

// Blah blah, write to the stream...

myStream.Position = 0;

using (var csp = new MD5CryptoServiceProvider()) {
    var myHash = csp.ComputeHash(myStream);
}

修改:避免积累大量流(Stream)的一种可能是在循环中多次调用此函数并对结果进行异或(XOR):

// Assuming we had this somewhere:
Byte[] myRunningHash = new Byte[16];

// Later on, from above:
for (var i = 0; i < 16; i++) // I believe MD5 are 16-byte arrays.  Edit accordingly.
    myRunningHash[i] = myRunningHash[i] ^ [myHash[i];

编辑#2:最终,基于下面@usr的答案,你可能可以使用HashCore和HashFinal:

using (var csp = new MD5CryptoServiceProvider()) {

    // My example here uses a foreach loop, but an 
    // event-driven stream-like approach is 
    // probably more what you are doing here.
    foreach (byte[] someData in myDataThings)
        csp.HashCore(someData, 0, someData.Length);

    var myHash = csp.HashFinal();
}

问题在于,除非我在使用流时出现问题,否则我不想要中间(内存)流(或额外的物化IO)。 - user166390
如果您已经有一个流,请直接使用它。否则,由于大多数真实的哈希算法都使用一个输入流,您可能需要一个中间流。如果您有字节数组,则可以按照@Matthew上面的答案使用TransformBlock方法。 - Chris Shain
我有一个输入流,我从中消耗数据(只能向前读取)。不幸的是,我没有另一个可用的流可以分配。此外,我需要能够将“操作”写入流中,因为知道会发生什么很重要。我可以使用MemoryStream,例如将其写入其中,然后将其用作ComputeHash的输入,但我希望有一种“运行中”的计算方式,而不会产生中间数据堆积。 - user166390
@pst 添加了 HashCore 和 HashFinal 的示例,可能是最好的方法。 - Chris Shain

0

这是规范的方式:

using System;
using System.Security.Cryptography;
using System.Text;

public void CreateHash(string sSourceData)
{
    byte[] sourceBytes;
    byte[] hashBytes;

    //create Bytearray from source data
    sourceBytes = ASCIIEncoding.ASCII.GetBytes(sSourceData);

    // calculate 16 Byte Hashcode
    hashBytes = new MD5CryptoServiceProvider().ComputeHash(sourceBytes);
    string sOutput = ByteArrayToHexString(hashBytes);
 }

static string ByteArrayToHexString(byte[] arrInput)
{
    int i;
    StringBuilder sOutput = new StringBuilder(arrInput.Length);
    for (i = 0; i < arrInput.Length - 1; i++)
    {
        sOutput.Append(arrInput[i].ToString("X2"));
    }
    return sOutput.ToString();
}

是的,我以前使用过这种方法,但它要求在“ComputeHash”函数调用时所有数据都已经存在 :( - user166390
好的,我明白了。任何TransformBlock实现都可以,比如MD5、SHA256等等。 - Mare Infinitus

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