【问题标题】:Choosing a hash function for best performance选择哈希函数以获得最佳性能
【发布时间】:2021-05-26 05:40:09
【问题描述】:

我需要比较网络上的许多文件(有些可能很大,有些可能很小)。 因此,我计划对每个客户端上的每个文件进行哈希处理,并仅通过网络发送哈希值。
这里的主要目标是性能。这意味着最小的网络流量。安全不是问题。
也应该有“零”冲突,因为我不想错误地认为两个不同的文件是相同的。话虽如此,我知道理论上总会有碰撞,我只是希望实际遇到它们的机会绝对可以忽略不计。

所以我的问题是:哪个 .net 哈希函数最适合这项任务?

我正在考虑使用缓冲阅读器和MD5CryptoServiceProvider(因为 CNG 可能并非在所有客户端上都可用)。

有没有办法获得比这更好的性能? (也许使用一些外部库?)

【问题讨论】:

  • 您的目标“性能”和“小碰撞机会”不能同时满足。哈希函数的计算成本更高的唯一原因是避免碰撞。您当前的陈述就像要求一种“使文件尽可能小”和“具有最佳性能”的压缩算法。您总是需要在两者之间进行权衡。
  • @dtech 我同意。我正在寻找最好的折衷方案——在性能和碰撞几率之间找到平衡……我只是不知道每个选项的几率和性能的细节。
  • 如果您不使用哈希函数(通过尝试查找冲突),那么发生冲突的可能性大约为 c/2^N,其中 c 是您的哈希(文件)数,N 是您的数位数(例如 MD5 为 128)。我只会使用 MD5,因为它是最快的广泛可用的算法之一,只要您不需要安全性就很好。请注意,您将总是需要处理冲突(即使您将它们留给用户),因为它们可能发生在每个散列函数中。 CRC32 也广泛可用且速度快,但仅使用 32 位,因此更可能发生冲突。
  • @dtech 问题是我无法处理冲突。由于我想完全避免通过网络发送整个文件,我能看到的唯一方法是假设文件在它们的哈希相同时是相同的。如果每 ~2^128 次操作一次,这个假设就会被打破,那么,这是一个非常大的数字 - 在此之前宇宙辐射是否更有可能翻转我的 ram 上的一些位?
  • 在这种情况下,您的碰撞处理是忽略它/让用户处理它,如果您的碰撞机会足够小,这很好。 2^128 确实很大。即使您有 2^32+1(约 43 亿)个文件,机会仍然很小(1/2^100),而 CRC32 将始终至少有一次冲突。

标签: .net .net-4.0 hash


【解决方案1】:

我遇到了类似的情况,我需要一个 .NET 散列算法。我需要它用于速度比安全更重要的服务器响应缓存。当我到达这个线程时,我注意到关于算法选择以及 32 位与 64 位执行的性能差异的猜测。为了在这场辩论中引入一些科学知识,我创建了一些代码来实际测试一些可用的算法。我决定测试内置的 MD5、SHA1、SHA256 和 SHA512 算法。我还包括了来自force-net 的CRC32 实现和来自DamienGKit 的CRC64 实现。我使用 ~115MB 文件的结果如下:

在 32 位模式下运行

热身阶段

CRC32:296 MiB/s [9C54580A] 在 390 毫秒内。

CRC64:95 MiB/s [636BCF1455BC885A] 在 1212 毫秒内。

MD5:191 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==],604 毫秒。

SHA1:165 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 699 毫秒内。

SHA256:93 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1240 毫秒内。

SHA512:47 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...] 在 2464 毫秒内。

最终运行

CRC32:279 MiB/s [9C54580A] 在 414 毫秒内。

CRC64:96 MiB/s [636BCF1455BC885A] 在 1203 毫秒内。

MD5:197 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==],588 毫秒。

SHA1:164 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 707 毫秒内。

SHA256:96 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1200 毫秒内。

SHA512:47 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...] 在 2441 毫秒内。


在 64 位模式下运行

热身阶段

CRC32:310 MiB/s [9C54580A] 在 373 毫秒内。

CRC64:117 MiB/s [636BCF1455BC885A] 在 986 毫秒内。

MD5:198 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==],584 毫秒。

SHA1:184 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...],627 毫秒。

SHA256:104 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1112 毫秒内。

SHA512:149 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...] 在 778 毫秒内。

最终运行

CRC32:292 MiB/s [9C54580A] 在 396 毫秒内。

CRC64:119 MiB/s [636BCF1455BC885A] 在 975 毫秒内。

MD5:199 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==],582 毫秒。

SHA1:192 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 601 毫秒内。

SHA256:106 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1091 毫秒内。

SHA512:157 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...] 在 738 毫秒内。

这些结果是从运行 .NET v4.5.2 的已编译发行版 ASP.NET 项目中获得的。 32 位和 64 位结果都来自同一台机器。在 Visual Studio 中,我通过Tools > Options > Projects and Solutions > Web Projects > Use the 64 bit version of IIS Express 更改了模式,同时更改了项目的Platform target

我们可以看到,虽然结果在运行中略有波动,但 CRC32(通过 force-net)是最快的,其次是 Microsoft 的 MD5 和 SHA1。奇怪的是,选择 DamienGKit 的 CRC64 而不是内置的 MD5 或 SHA1 并没有性能优势。 64 位执行似乎对 SHA512 有很大帮助,但对其他人的帮助不大。

为了回答 OP 的问题,内置的 MD5 或 SHA1 似乎可以提供碰撞避免和性能的最佳平衡

我的代码如下:

Stopwatch timer = new Stopwatch();
Force.Crc32.Crc32Algorithm hasherCRC32 = new Force.Crc32.Crc32Algorithm();
System.Security.Cryptography.MD5Cng hasherMD5 = new System.Security.Cryptography.MD5Cng();
System.Security.Cryptography.SHA1Cng hasherSHA1 = new System.Security.Cryptography.SHA1Cng();
System.Security.Cryptography.SHA256Cng hasherSHA256 = new System.Security.Cryptography.SHA256Cng();
System.Security.Cryptography.SHA512Cng hasherSHA512 = new System.Security.Cryptography.SHA512Cng();
String result = "";
String rate = "";

Status.Text += "Running in " + ((IntPtr.Size == 8) ? "64" : "32") + "-bit mode.<br /><br />";

Status.Text += "Warm-up phase:<br />";

timer.Restart();
result = BitConverter.ToUInt32(hasherCRC32.ComputeHash(ImageUploader.FileBytes), 0).ToString("X8");
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "CRC32: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = DamienG.Security.Cryptography.Crc64Iso.Compute(ImageUploader.FileBytes).ToString("X16");
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "CRC64: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherMD5.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "MD5: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA1.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA1: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA256.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA256: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA512.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA512: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

Status.Text += "<br />Final run:<br />";

timer.Restart();
result = BitConverter.ToUInt32(hasherCRC32.ComputeHash(ImageUploader.FileBytes), 0).ToString("X8");
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "CRC32: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = DamienG.Security.Cryptography.Crc64Iso.Compute(ImageUploader.FileBytes).ToString("X16");
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "CRC64: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherMD5.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "MD5: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA1.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA1: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA256.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA256: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

timer.Restart();
result = Convert.ToBase64String(hasherSHA512.ComputeHash(ImageUploader.FileBytes));
timer.Stop();
rate = ((double)ImageUploader.FileBytes.Length / timer.ElapsedMilliseconds / 1.024 / 1024).ToString("0");
Status.Text += "SHA512: " + rate + " MiB/s [" + result + "] in " + timer.ElapsedMilliseconds + "ms" + ".<br />";

【讨论】:

    【解决方案2】:

    这取决于您拥有的文件数量。

    P(collision) = c/2^N 发生冲突的可能性(在完美的哈希函数中),其中 c 是您的消息(文件)数,N 是您的冲突算法中的位数。

    由于现实世界的哈希函数并不完美,因此您有两种选择:优化速度和优化避免碰撞。

    在第一种情况下,您需要使用CRC32。 CRC32 很常见,但根据您拥有的文件数量,可能还不够:保证在大约 43 亿条消息(32 个有效位)处发生冲突,但实际上您可能会遇到第一次冲突大约 1000 万条消息。 CRC32 的实现速度非常快(SSE 4.2 甚至有一个硬件指令)。 CRC64 发生冲突的可能性要低得多,但并未被广泛使用,因此如果您想要比 CRC32 更多地避免冲突,您最好查看加密哈希函数。

    如果您想在牺牲速度的同时避免冲突,您将需要加密哈希函数,其中 MD5(128 位)、SHA-1(160 位)和 SHA-2(通常是 SHA-256 或 SHA-512)是最广泛使用并具有快速实现。可以使用非常有效的 MD5 哈希冲突查找算法,但如果您输入随机消息,您将尽可能接近P(collision) = c/2^128,同时仍在合理的时间内运行。

    【讨论】:

    • 另请注意,SHA-512 在 64 位处理器上实际上比 SHA-256 快,因此您通常希望使用 SHA-512。
    • 一个很好的答案;尽管我的 cmets 和我自己的答案,哈希可能是这里最好的解决方案。我认为 OP 过分强调对“性能”的需求。大多数哈希“表现”都足够好。
    【解决方案3】:

    测试结果分析

    我将进一步分析内置 PHP 哈希的性能,除了 Michael 之前在此处所做的分析(请参阅上面的帖子),因为这个主题非常有趣并且有意想不到的结果。

    结果并不那么明显,甚至令人惊讶。一种简单的算法——CRC32 或 CRC16,比复杂的算法——MD5 慢。似乎现代 CPU 不喜欢特定的旧算法并且执行它们非常缓慢,至少在算法没有以新方式实现时,利用现代 CPU 架构。 CRC16 CCITT 算法在过去有 300 BPS 拨号调制解调器的情况下是相对快速和高效的。现在有专门为新硬件设计的现代算法,它在相同硬件上的运行速度可能比旧算法快得多,旧算法本质上不适合新硬件,即使你尝试优化它们,它们无论如何都会相对较慢。例如,对于每个字节都依赖于前一个字节的算法,您无法利用乱序执行或 64 位寄存器并并行处理许多位。

    您可能会从其他加密库中看到证实了我们在 PHP 中看到的内容 - CRC32 IEEE 具有与 MD5 几乎相同的全速。这是另一个库结果的链接:https://www.cryptopp.com/benchmarks.html

    OpenSSL 显示了类似的结果。乍一看,这似乎很不合理,因为 CRC32 的算法比 MD5 的算法简单得多,但实际情况恰恰相反。

    我只是想展示一下 CRC32 函数有多么简单。

    这是用下一个传入字节 (Delphi) 更新 CRCR32 计数器的代码:

       // Returns an updated CRC32
      function UpdateCrc32(CurByte: Byte; CurCrc: Cardinal): Cardinal; inline;
      begin
        UpdateCrc32 := Crc32Table[Byte(CurCrc xor CurByte)] xor (CurCrc shr 8);
      end;
    

    这是汇编代码:

    @calc_crc32:
        xor    dl,[esi]
        mov    al,dl
        shr    edx,8
        xor    edx,dword ptr [edi+eax*4]
        inc    esi
        loop   @calc_crc32
    

    您也可以展开这段代码,因此每个字节只会得到 5 条 CPU 指令:

        xor    dl,bl
        shr    rbx,8
        mov    al,dl
        shr    edx,8
        xor    edx,dword ptr [r8+rax*4]
    

    您只需将接下来的 8 个字节数据加载到 rbx 寄存器,然后重复此代码 8 次,直到您需要将接下来的 8 个字节加载到 rbx 64 位寄存器。

    这里是计算整个字符串的CRC32的例程:

        function CalcCRC32(const B; Size: NativeUINT;
        const 
          InitialValue: Cardinal = CRC32_INIT): Cardinal;
        var
          C: Cardinal;
          P: PAnsiChar;
          i: NativeUINT;
        begin
          C := InitialValue;
          if Size > 0 then
          begin
            P := @B;
            for i := 0 to Size - 1 do
              C := UpdateCrc32(Byte(P[i]), C);
          end;
          Result := C;
        end;
    

    下面是 Delphi 将它编译成机器代码的方式——不是非常优化,但非常简单——每个字节只有 11 条汇编指令,令人惊讶的是,在 Intel Core i5-6600 上的工作速度比上述汇编器快一点即使在循环展开后代码。如您所见,实现 CRC32 IEEE 的所有这些指令都很简单,没有循环或比较,每个字节末尾只有一个比较。这只是编译的 Delphi 代码的调试器输出,而不是人工编写的汇编代码。

        CRC32.pas.78: begin
                                        push esi
                                        push edi
        CRC32.pas.80: if Size > 0 then
                                        test edx,edx
                                        jbe $00500601
        CRC32.pas.82: P := @B;
                                        mov edi,eax
        CRC32.pas.83: for i := 0 to Size - 1 do
                                        mov eax,edx
                                        dec eax
                                        test eax,eax
                                        jb $00500601
                                        inc eax
                                        xor esi,esi
        CRC32.pas.84: C := UpdateCrc32(Byte(P[i]), C);
                                        movzx edx,[edi+esi]
                                        xor dl,cl
                                        movzx edx,dl
                                        mov edx,[edx*4+$517dec]
                                        shr ecx,$08
                                        xor edx,ecx
                                        mov ecx,edx
                                        inc esi
        CRC32.pas.83: for i := 0 to Size - 1 do
                                        dec eax
                                        jnz $005005e6
        CRC32.pas.86: Result := C;
                                        mov eax,ecx
        CRC32.pas.87: end;
                                        pop edi
                                        pop esi
                                        ret
    

    这里是 CRC32 汇编代码的另一种变体,每个字节只有 5 个处理器命令,而不是 11 个,但它与上面的汇编代码基本相同,只是使用不同的寄存器并避免了再次出现的“循环”命令在 i5-6600 上比两个不同的指令快。你可以在CRC32 assembler function called from C console app找到整个代码

                 586
                .model flat, stdcall 
                .xmm
                .data
                .code
            CRC32 proc sizeOfFile:DWORD, file:DWORD
                push    esi
                push    ecx
                push    edx
    
                mov esi, file
                xor edx, edx
                or  eax, -1
                mov ecx, sizeOfFile
        
            CRC32_loop:
                mov dl, byte ptr [esi]
                xor dl, al
                shr eax, 8
                xor eax, dword ptr [crc32_table + 4*edx]
                inc esi
                dec ecx
                jnz CRC32_loop
        
                not eax
        
                pop edx
                pop ecx
                pop esi
                ret
        
    

    现在将它与 MD5 和 Peter Sawatzki 高度优化的汇编代码进行比较:

    ; MD5_386.Asm   -  386 optimized helper routine for calculating
    ;                  MD Message-Digest values
    ; written 2/2/94 by
    ;
    ; Peter Sawatzki
    ; Buchenhof 3
    ; D58091 Hagen, Germany Fed Rep
    ;
    ; EMail: Peter@Sawatzki.de
    ; EMail: 100031.3002@compuserve.com
    ; WWW:   http://www.sawatzki.de
    ;
    ;
    ; original C Source was found in Dr. Dobbs Journal Sep 91
    ; MD5 algorithm from RSA Data Security, Inc.
    
    .386
    .MODEL FLAT
    .CODE
    
    R1 = ESi
    R2 = EDi
    
    FF Macro a,b,c,d,x,s,ac
    ; a:= ROL (a+x+ac + (b And c Or Not b And d), s) + b
      Add a, [EBp+(4*x)]
      Add a, ac
      Mov R1, b
      Not R1
      And R1, d
      Mov R2, c
      And R2, b
      Or  R1, R2
      Add a, R1
      Rol a, s
      Add a, b
    EndM
    
    GG Macro a,b,c,d,x,s,ac
    ; a:= ROL (a+x+ac + (b And d Or c And Not d), s) + b
      Add a, [EBp+(4*x)]
      Add a, ac
      Mov R1, d
      Not R1
      And R1, c
      Mov R2, d
      And R2, b
      Or  R1, R2
      Add a, R1
      Rol a, s
      Add a, b
    EndM
    
    HH Macro a,b,c,d,x,s,ac
    ; a:= ROL (a+x+ac + (b Xor c Xor d), s) + b
      Add a, [EBp+(4*x)]
      Add a, ac
      Mov R1, d
      Xor R1, c
      Xor R1, b
      Add a, R1
      Rol a, s
      Add a, b
    EndM
    
    II Macro a,b,c,d,x,s,ac
    ; a:= ROL (a+x+ac + (c Xor (b Or Not d)), s) + b
      Add a, [EBp+(4*x)]
      Add a, ac
      Mov R1, d
      Not R1
      Or  R1, b
      Xor R1, c
      Add a, R1
      Rol a, s
      Add a, b
    EndM
    
    Transform Proc
    Public Transform
    ;Procedure Transform (Var Accu; Const Buf); Register;
    
    ; save registers that Delphi requires to be restored
      Push EBx
      Push ESi
      Push EDi
      Push EBp
    
      Mov EBp, EDx ; Buf -> EBp
      Push EAx     ; Accu -> Stack
      Mov EDx, [EAx+12]
      Mov ECx, [EAx+8]
      Mov EBx, [EAx+4]
      Mov EAx, [EAx]
    
      FF EAx,EBx,ECx,EDx,  0,  7, 0d76aa478h  ; 1
      FF EDx,EAx,EBx,ECx,  1, 12, 0e8c7b756h  ; 2
      FF ECx,EDx,EAx,EBx,  2, 17, 0242070dbh  ; 3
      FF EBx,ECx,EDx,EAx,  3, 22, 0c1bdceeeh  ; 4
      FF EAx,EBx,ECx,EDx,  4,  7, 0f57c0fafh  ; 5
      FF EDx,EAx,EBx,ECx,  5, 12, 04787c62ah  ; 6
      FF ECx,EDx,EAx,EBx,  6, 17, 0a8304613h  ; 7
      FF EBx,ECx,EDx,EAx,  7, 22, 0fd469501h  ; 8
      FF EAx,EBx,ECx,EDx,  8,  7, 0698098d8h  ; 9
      FF EDx,EAx,EBx,ECx,  9, 12, 08b44f7afh  ; 10
      FF ECx,EDx,EAx,EBx, 10, 17, 0ffff5bb1h  ; 11
      FF EBx,ECx,EDx,EAx, 11, 22, 0895cd7beh  ; 12
      FF EAx,EBx,ECx,EDx, 12,  7, 06b901122h  ; 13
      FF EDx,EAx,EBx,ECx, 13, 12, 0fd987193h  ; 14
      FF ECx,EDx,EAx,EBx, 14, 17, 0a679438eh  ; 15
      FF EBx,ECx,EDx,EAx, 15, 22, 049b40821h  ; 16
    
      GG EAx,EBx,ECx,EDx,  1,  5, 0f61e2562h  ; 17
      GG EDx,EAx,EBx,ECx,  6,  9, 0c040b340h  ; 18
      GG ECx,EDx,EAx,EBx, 11, 14, 0265e5a51h  ; 19
      GG EBx,ECx,EDx,EAx,  0, 20, 0e9b6c7aah  ; 20
      GG EAx,EBx,ECx,EDx,  5,  5, 0d62f105dh  ; 21
      GG EDx,EAx,EBx,ECx, 10,  9, 002441453h  ; 22
      GG ECx,EDx,EAx,EBx, 15, 14, 0d8a1e681h  ; 23
      GG EBx,ECx,EDx,EAx,  4, 20, 0e7d3fbc8h  ; 24
      GG EAx,EBx,ECx,EDx,  9,  5, 021e1cde6h  ; 25
      GG EDx,EAx,EBx,ECx, 14,  9, 0c33707d6h  ; 26
      GG ECx,EDx,EAx,EBx,  3, 14, 0f4d50d87h  ; 27
      GG EBx,ECx,EDx,EAx,  8, 20, 0455a14edh  ; 28
      GG EAx,EBx,ECx,EDx, 13,  5, 0a9e3e905h  ; 29
      GG EDx,EAx,EBx,ECx,  2,  9, 0fcefa3f8h  ; 30
      GG ECx,EDx,EAx,EBx,  7, 14, 0676f02d9h  ; 31
      GG EBx,ECx,EDx,EAx, 12, 20, 08d2a4c8ah  ; 32
    
      HH EAx,EBx,ECx,EDx,  5,  4, 0fffa3942h  ; 33
      HH EDx,EAx,EBx,ECx,  8, 11, 08771f681h  ; 34
      HH ECx,EDx,EAx,EBx, 11, 16, 06d9d6122h  ; 35
      HH EBx,ECx,EDx,EAx, 14, 23, 0fde5380ch  ; 36
      HH EAx,EBx,ECx,EDx,  1,  4, 0a4beea44h  ; 37
      HH EDx,EAx,EBx,ECx,  4, 11, 04bdecfa9h  ; 38
      HH ECx,EDx,EAx,EBx,  7, 16, 0f6bb4b60h  ; 39
      HH EBx,ECx,EDx,EAx, 10, 23, 0bebfbc70h  ; 40
      HH EAx,EBx,ECx,EDx, 13,  4, 0289b7ec6h  ; 41
      HH EDx,EAx,EBx,ECx,  0, 11, 0eaa127fah  ; 42
      HH ECx,EDx,EAx,EBx,  3, 16, 0d4ef3085h  ; 43
      HH EBx,ECx,EDx,EAx,  6, 23, 004881d05h  ; 44
      HH EAx,EBx,ECx,EDx,  9,  4, 0d9d4d039h  ; 45
      HH EDx,EAx,EBx,ECx, 12, 11, 0e6db99e5h  ; 46
      HH ECx,EDx,EAx,EBx, 15, 16, 01fa27cf8h  ; 47
      HH EBx,ECx,EDx,EAx,  2, 23, 0c4ac5665h  ; 48
    
      II EAx,EBx,ECx,EDx,  0,  6, 0f4292244h  ; 49
      II EDx,EAx,EBx,ECx,  7, 10, 0432aff97h  ; 50
      II ECx,EDx,EAx,EBx, 14, 15, 0ab9423a7h  ; 51
      II EBx,ECx,EDx,EAx,  5, 21, 0fc93a039h  ; 52
      II EAx,EBx,ECx,EDx, 12,  6, 0655b59c3h  ; 53
      II EDx,EAx,EBx,ECx,  3, 10, 08f0ccc92h  ; 54
      II ECx,EDx,EAx,EBx, 10, 15, 0ffeff47dh  ; 55
      II EBx,ECx,EDx,EAx,  1, 21, 085845dd1h  ; 56
      II EAx,EBx,ECx,EDx,  8,  6, 06fa87e4fh  ; 57
      II EDx,EAx,EBx,ECx, 15, 10, 0fe2ce6e0h  ; 58
      II ECx,EDx,EAx,EBx,  6, 15, 0a3014314h  ; 59
      II EBx,ECx,EDx,EAx, 13, 21, 04e0811a1h  ; 60
      II EAx,EBx,ECx,EDx,  4,  6, 0f7537e82h  ; 61
      II EDx,EAx,EBx,ECx, 11, 10, 0bd3af235h  ; 62
      II ECx,EDx,EAx,EBx,  2, 15, 02ad7d2bbh  ; 63
      II EBx,ECx,EDx,EAx,  9, 21, 0eb86d391h  ; 64
    
      Pop ESi            ; get Accu from stack
      Add [ESi],    EAx
      Add [ESi+4],  EBx
      Add [ESi+8],  ECx
      Add [ESi+12], EDx
    
    ; restore registers for Delphi
      Pop EBp
      Pop EDi
      Pop ESi
      Pop EBx
    
      Ret
      Transform EndP
    
      End
    

    您可以在https://github.com/maximmasiutin/MD5_Transform-x64找到此代码的 32 位和 64 位版本

    此代码在 IA-32 或 x86-64 下的性能是每字节数据(在 Skylake 上)需要 4.94 个 CPU 周期来计算 MD5。

    上面的代码一次性处理64字节的传入数据。它从执行准备步骤的主例程中调用:

        procedure CiphersMD5Update(var Context: TMD5Ctx; const ChkBuf; len:         UInt32);
        var
          BufPtr: ^Byte;
          Left: UInt32;
        begin
          If Context.Count[0] + UInt32(len) shl 3 < Context.Count[0] then
            Inc(Context.Count[1]);
          Inc(Context.Count[0], UInt32(len) shl 3);
          Inc(Context.Count[1], UInt32(len) shr 29);
        
          BufPtr := @ChkBuf;
          if Context.BLen > 0 then
          begin
            Left := 64 - Context.BLen;
            if Left > len then
              Left := len;
            Move(BufPtr^, Context.Buffer[Context.BLen], Left);
            Inc(Context.BLen, Left);
            Inc(BufPtr, Left);
            If Context.BLen < 64 then
              Exit;
            Transform(Context.State, @Context.Buffer);
            Context.BLen := 0;
            Dec(len, Left)
          end;
          while len >= 64 do
          begin
            Transform(Context.State, BufPtr);
            Inc(BufPtr, 64);
            Dec(len, 64)
          end;
          if len > 0 then
          begin
            Context.BLen := len;
            Move(BufPtr^, Context.Buffer[0], Context.BLen)
          end
        end;
    
        
    

    如果您的处理器支持 CRC32 操作码 (SSE 4.2),您可以使用以下代码将校验和的计算速度提高 10 倍:

          function crc32csse42(crc: cardinal; buf: Pointer; len: NativeUInt): cardinal;
          asm // ecx=crc, rdx=buf, r8=len
            .NOFRAME
            mov eax,ecx
            not eax
            test r8,r8;   jz @0
            test rdx,rdx; jz @0
     @7:    test rdx,7;   jz @8 // align to 8 bytes boundary
            crc32 eax,byte ptr [rdx]
            inc rdx
            dec r8;     jz @0
            test rdx,7; jnz @7
     @8:    mov rcx,r8
            shr r8,3
            jz @2
     @1:    crc32 eax,qword ptr [rdx] // calculate CRC of 8 bytes, aligned
            dec r8
            lea rdx,rdx+8
            jnz @1
     @2:    // less than 8 bytes remaining
            and rcx,7; jz @0
            cmp rcx,4; jb @4
            crc32 eax,dword ptr [rdx] // calculate CRC of 4 bytes
            sub rcx,4
            lea rdx,rdx+4
            jz @0
    @4:     // less than 4 bytes remaining
            crc32 eax,byte ptr [rdx]
            dec rcx; jz @0
            crc32 eax,byte ptr [rdx+1]
            dec rcx; jz @0
            crc32 eax,byte ptr [rdx+2]
    @0:     not eax
          end;
    

    请注意,在我的示例中,我使用的缓冲区仅为 5KB,以适应处理器的缓存并排除较慢 RAM 对摘要计算速度的影响。

    对于不使用 CRC32 操作码但利用乱序执行(包括通过寄存器重命名的推测执行)的现代处理器,有 CRC32 算法的快速实现。这种实现的一个例子是 CRC32 Slicing-By-8。 IA-32 或 x86-64 汇编代码每字节数据有 1,20 个 CPU 时钟周期(在 Skylake 上)。你可以在https://github.com/maximmasiutin/CRC32-Slicing-x64-Asm-Pas找到这样的实现@

    在 PHP 中,即使在版本 7 中,似乎也不支持 CRC32 的硬件加速,尽管 Intel 和 AMD 处理器自古以来就支持这些指令。英特尔从 2008 年 11 月开始支持 CRC32(Nehalem(微架构)),而 AMD 似乎从 2013 年开始支持它。

    确认迈克尔结果的我自己的测试

    我已经在不同配置下测试了各种 PHP 哈希函数:(1) AMD FX-8320(2012 年发布)在 Ubuntu 下使用 PHP 5,以及 (2) Intel Core i5-6600 在 2015 年在 Windows 下使用 PHP 7 发布。我还在这个 Intel Core i5-6600 上运行了 OpenSSL 测试。除此之外,我还对我们在软件“The Bat!”中使用的加密程序进行了测试。用德尔福写的。虽然主要软件是用 Delphi 编写的,但我们使用的加密例程是用 Intel 处理器(32 位或 64 位)的 Assembler 或 C 语言编写的。

    我发现我们的 Delphi 代码在各种散列函数和数据大小之间显示出非常大的速度差异。这与 PHP 形成鲜明对比,PHP 在某种程度上,除了极少数例外,从最简单的 CRC32 到曾经加密的强大 MD5 的所有哈希函数都具有几乎相同的直通输出速度。

    以下是我在 AMD FX-8320、PHP5、Ubuntu 上进行的测量。我做了两个测试用例。首先,我运行 5000 次迭代来散列仅包含 5 个字节的消息。通过这个小消息大小,我打算测试各种算法的初始化/完成步骤的持续时间,以及它如何影响整体性能。对于某些算法,如 CRC32,三个实际上没有最终确定步骤——摘要总是在每个字节之后准备好。像 SHA1 或 MD5 或其他加密强大的函数具有将较大的上下文压缩为较小的最终摘要的最终步骤。其次,我运行 5000 次迭代来散列 5000 字节长的消息。两条消息都预先填充了伪随机字节(每次迭代后它们都不会重新填充;它们只在程序启动时填充一次)。

    我的 PHP 哈希速度测试结果

    我已经修改了您的 PHP 代码,使其现在适用于 PHP5 和 PHP7,在不同版本的 PHP 中生成随机数据的函数不同。我刚刚测量了哈希 5000 次 5 字节消息迭代和 5000 次 5000 字节消息迭代所需的时间。结果如下:

            Legend:
            (1) 5b x 5000, AMD FX-8320, PHP5
            (2) 5000b x 5000, AMD FX-8320, PHP5
    
    PHP hash              (1)            (2)
    --------         ------------   ------------
    md2              0.021267 sec   2.602651 sec
    md4              0.002684 sec   0.035243 sec
    md5              0.002570 sec   0.055548 sec
    sha1             0.003346 sec   0.106432 sec
    sha224           0.004945 sec   0.210954 sec
    sha256           0.004735 sec   0.238030 sec
    sha384           0.005848 sec   0.144015 sec
    sha512           0.006085 sec   0.142884 sec
    ripemd128        0.003385 sec   0.120959 sec
    ripemd160        0.004164 sec   0.174045 sec
    ripemd256        0.003487 sec   0.121477 sec
    ripemd320        0.004206 sec   0.177473 sec
    whirlpool        0.009713 sec   0.509682 sec
    tiger128,3       0.003414 sec   0.059028 sec
    tiger160,3       0.004354 sec   0.059335 sec
    tiger192,3       0.003379 sec   0.058891 sec
    tiger128,4       0.003514 sec   0.073468 sec
    tiger160,4       0.003602 sec   0.072329 sec
    tiger192,4       0.003507 sec   0.071856 sec
    snefru           0.022101 sec   1.190888 sec
    snefru256        0.021972 sec   1.217704 sec
    gost             0.013961 sec   0.653600 sec
    adler32          0.001459 sec   0.038849 sec
    crc32            0.001429 sec   0.068742 sec
    crc32b           0.001553 sec   0.063308 sec
    fnv132           0.001431 sec   0.038256 sec
    fnv164           0.001586 sec   0.060622 sec
    joaat            0.001569 sec   0.062947 sec
    haval128,3       0.006747 sec   0.174759 sec
    haval160,3       0.005810 sec   0.166154 sec
    haval192,3       0.006129 sec   0.168382 sec
    haval224,3       0.005918 sec   0.166792 sec
    haval256,3       0.006119 sec   0.173360 sec
    haval128,4       0.007364 sec   0.233829 sec
    haval160,4       0.007917 sec   0.240273 sec
    haval192,4       0.007676 sec   0.245864 sec
    haval224,4       0.007580 sec   0.245249 sec
    haval256,4       0.007442 sec   0.241091 sec
    haval128,5       0.008651 sec   0.281248 sec
    haval160,5       0.009304 sec   0.278619 sec
    haval192,5       0.008972 sec   0.281235 sec
    haval224,5       0.008917 sec   0.274923 sec
    haval256,5       0.008853 sec   0.282171 sec
    

    然后我在 Intel Core i5-6600 下运行相同的 PHP 脚本,在 Windows 10 下使用 64 位版本的 PHP7。结果如下:

            Legend:
            (1) 5b x 5000, Intel Core i5-6600, PHP7
            (2) 5000b x 5000, Intel Core i5-6600, PHP7
    
    
    PHP hash           (1)            (2)
    ---------    ------------    ------------
    md2          0.016131 sec    2.308100 sec
    md4          0.001218 sec    0.040803 sec
    md5          0.001284 sec    0.046208 sec
    sha1         0.001499 sec    0.050259 sec
    sha224       0.002683 sec    0.120510 sec
    sha256       0.002297 sec    0.119602 sec
    sha384       0.002792 sec    0.080670 sec
    ripemd128    0.001984 sec    0.094280 sec
    ripemd160    0.002514 sec    0.128295 sec
    ripemd256    0.002015 sec    0.093887 sec
    ripemd320    0.002748 sec    0.128955 sec
    whirlpool    0.003402 sec    0.271102 sec
    tiger128,3   0.001282 sec    0.038638 sec
    tiger160,3   0.001305 sec    0.037155 sec
    tiger192,3   0.001309 sec    0.037684 sec
    tiger128,4   0.001618 sec    0.050690 sec
    tiger160,4   0.001571 sec    0.049656 sec
    tiger192,4   0.001711 sec    0.050682 sec
    snefru       0.010949 sec    0.865108 sec
    snefru256    0.011587 sec    0.867685 sec
    gost         0.008968 sec    0.449647 sec
    adler32      0.000588 sec    0.014345 sec
    crc32        0.000609 sec    0.079202 sec
    crc32b       0.000636 sec    0.074408 sec
    fnv132       0.000570 sec    0.028157 sec
    fnv164       0.000566 sec    0.028776 sec
    joaat        0.000623 sec    0.042127 sec
    haval128,3   0.002972 sec    0.084010 sec
    haval160,3   0.002968 sec    0.083213 sec
    haval192,3   0.002943 sec    0.082217 sec
    haval224,3   0.002798 sec    0.084726 sec
    haval256,3   0.002995 sec    0.082568 sec
    haval128,4   0.003659 sec    0.112680 sec
    haval160,4   0.003858 sec    0.111462 sec
    haval192,4   0.003526 sec    0.112510 sec
    haval224,4   0.003671 sec    0.111656 sec
    haval256,4   0.003636 sec    0.111236 sec
    haval128,5   0.004488 sec    0.140130 sec
    haval160,5   0.005095 sec    0.137777 sec
    haval192,5   0.004117 sec    0.140711 sec
    haval224,5   0.004311 sec    0.139564 sec
    haval256,5   0.004382 sec    0.138345 sec
    

    如您所见,要在 PHP 中计算消息的 CRC32,在我几乎所有的测试中,计算相同消息的 MD5 所需的时间大约只有一半。唯一的例外是在英特尔酷睿 i5-6600 上使用 PHP7 测试 5000 条 5000 字节的消息时,CRC32 甚至比 MD5 花费的时间更长(!)。这个奇怪的结果在我的案例中总是可以重复的。我找不到合理的解释。

    另外,在 PHP 上,MD5 和 SHA1 之间几乎没有明显的速度差异,除了在带有 PHP5 的 Ubuntu 上,在测试 5000 条 5000 字节的消息时,MD5 快了两倍。

    我的 OpenSSL 哈希性能测试结果

    以下是 OpenSSL 在 Intel i5-660 上的测试。它们以不同的方式显示数据。它们没有显示消化某些数据集需要多少时间,但反之亦然:它们显示了 OpenSSL 在 3 秒内设法散列的数据量。所以,价值越高越好:

    Legend:
     (1) OpenSSL 1.1.0 on Intel Core i5-6600, number of 16-bytes messages processed in 3 seconds
     (2) OpenSSL 1.1.0 on Intel Core i5-6600, number of 8192-bytes messages processed in 3 seconds
    
    
    Algorighm              (1)            (2)
    ---------         ---------      ----------
    md4               50390.16k      817875.48k
    md5              115875.35k      680700.59k
    sha1             118158.30k      995986.09k
    ripemd160         30308.79k      213224.11k
    whirlpool         39605.02k      182072.66k
    

    再次,md5 和 sha1 之间几乎没有区别,这很奇怪 并且需要进一步研究MD5和SHA-1算法在时间消耗方面是否本质上是相同的。

    我们的 Delphi 哈希函数性能结果

    这是我们在英特尔酷睿 i5-6600 上在 Windows 10 64 位下的 Delphi 库的结果,测试的代码是 32 位 Win32 应用程序。

          Legend:
            (1) Delphi, 5b x 5000 iterations
            (2) Delphi, 5000b x 5000 iterations
    
    Algorighm                  (1)                     (2)
    ---------------      --------------         --------------
    md2                  0.0381010 secs         5.8495807 secs
    md5                  0.0005015 secs         0.0376252 secs
    sha1                 0.0050118 secs         0.1830871 secs
    crc32               >0.0000001 secs         0.0581535 secs
    crc32c (intel hw)   >0.0000001 secs         0.0055349 secs
    

    如您所见,MD2 也比其他哈希值更小——与 PHP 代码的结果相同,但 MD5 比 SHA-1 快得多,总体而言,在 Delphi 中执行相同操作所需的时间更少与 PHP 相同的机器 例如,PHP7 用 MD5 消化 5000 条 5 字节消息需要 0.001284 秒,用 SHA1 需要 0.001499 秒。大约 5000 字节的消息 - PHP7 使用 MD5 耗时 0.046208 秒,使用 SHA-1 耗时 0.050259 秒。

    对于 Delphi,使用 MD5 消化 5000 条 5 字节消息需要 0.0005015 秒,而使用 SHA1 需要 0.0050118 秒。大约 5000 字节的消息——使用 MD5 需要 Delphi 0.0376252 秒,使用 SHA-1 需要 0.1830871 秒。如您所见,MD5 在 Delphi 中运行得更快,但 SHA-1 几乎相同。此外,Delphi 处理 5 字节消息的速度大约快 10 倍,但对于大约 5000 字节的消息,它与 SHA-1 大致相同甚至更慢。

    但在 CRC32 和 CRC32C 方面,Delphi 是无与伦比的,比 PHP 快 10 到 1000 倍。

    结论

    PHP 在循环和小型操作上天生就很慢。因此,如果您需要计算一条小消息的哈希值,那么在 PHP 上调用哪个哈希函数并不重要。但如果你需要消化一条大消息,算法速度的差异就开始显现出来:例如,MD2 的性能大约是 MD5 的十倍。无论如何,今天绝对没有理由使用 MD2。对于使用 MD2 的 PHP 用户而言,相比 MD5 没有任何优势。至于 MD5,最初设计为加密哈希函数,然后在 RFC-1991 中被 PGP 使用,现在不能再用于密码学,但可以在可信环境中用作校验和,例如用于 ETag 或其他方式。这个函数在正确实现时非常快(并且在 PHP 上,它至少不慢),与其他函数相比,MD5 产生了非常紧凑的摘要。这是我进行这些基准测试的 PHP 代码。我是根据 Michael 的原始代码示例制作的(见上文)。

    <?
     define (TRAILING_ZEROS, 6);
     $strlens = array(5, 30, 90, 1000, 5000);
     $hashes = hash_algos();
    
     function generate_bytes($len)
     {
       if (function_exists('random_bytes')) {$fn='random_bytes';$str = random_bytes($len);} else // for php 5
       if (function_exists('openssl_random_pseudo_bytes')) {$fn='openssl_random_pseudo_bytes';$str = openssl_random_pseudo_bytes($strlen);} else // for php 7
       {
            flush();
            ob_start () ;
            phpinfo () ;
            $str = str_pad(substr(ob_get_contents (), 0, $len), $len) ;
            ob_end_clean () ;
            $fn = 'phpinfo';
       }
       return array(0=>$str, 1=>$fn);
     }
    
     foreach ($strlens as $strlen)
     {
    
     $loops = 5000;
     echo "<h1>$loops iterations on $strlen bytes message</h1>".PHP_EOL;
     echo '<p>';
     $r = generate_bytes($strlen);
     $str = $r[0];
     $gotlen = strlen($str);
     while ($gotlen < $strlen)
     {
       // for some uncodumented reason, the  openssl_random_pseudo_bytes returned less bytes than needed
       $left = $strlen-$gotlen;
       echo "The ".$r[1]."() function returned $left byes less, trying again to get these remaining bytes only<br>";
       $r = generate_bytes($left);
       $str.= $r[0];
       $gotlen = strlen($str);
     };
    
     echo "Got the whole string of ".strlen($str)." bytes!";
     echo '</p>';
     echo PHP_EOL;
     echo "<pre>";
    
     foreach ($hashes as $hash)
     {
            $tss = microtime(true);
            for($i=0; $i<$loops; $i++)
            {
                    $x = hash($hash, $str, true);
            }
            $tse = microtime(true);
            echo "\n".str_pad($hash, 15, ' ')."\t" . str_pad(round($tse-$tss, TRAILING_ZEROS), TRAILING_ZEROS+2, '0') . " sec \t" . bin2hex($x);
     }
    
     echo PHP_EOL."</pre>".PHP_EOL;
     flush();
     }
    ?>
    

    Continuation of the post...

    【讨论】:

    • 我只是想说感谢您为世界提供了对 PHP、OpenSSL 和 Delphi 下的哈希函数的详细分析和讨论。很多时候,网上的人纯粹基于假设进行辩论,没有经过实际测试。即使在计算机的抽象世界中,事情也不总是遵循一个人的假设。例如,我发现 long doubledouble 执行(当我几年前尝试时)比 int 快得多float,至少在使用 Microsoft Visual C++ 编译的代码中。使用的确切硬件、操作系统和库会产生很大的不同。
    【解决方案4】:

    哈希函数不是为了速度而构建的,因此它们不是这项工作的好人选。在这种情况下,它们的优势(加密安全)也无关紧要。

    您应该考虑使用 CRC 或其他校验和函数;有一个常用的列表hereHashLib 有现成的实现。

    【讨论】:

    • 我想到散列函数的原因是我仍然想要随机碰撞的“零”机会。有很多文件需要比较,我不想 - 永远 - 错误地认为两个不同的文件是相等的。 CRC 或其他校验和是否足够强大?
    • "Zero" 和 zero 是两个不同的东西。此外,您的问题以粗体显示“这里的邮件目标是性能”,但根本没有提到零。那么,你真正想要什么?
    • @Amir 任何散列怎么可能有“零”的冲突机会?这是一个棘手的问题:他们不能!
    • 我知道,我只需要“零”碰撞,而不是零碰撞。 - 我已经更新了问题。感谢 cmets。
    • @Amir 您说您的重点是性能,而不是安全性。你已经得到了你的答案。如果你想继续使用哈希,那就试试吧!没有人阻止你。
    【解决方案5】:

    我相信您在这种情况下误解了哈希的目的。

    这是快速“这些是相同”的检查。哈希无法告诉您两个事物是否相等,因为它们发生冲突。

    因此,考虑到简单的 CRC 很少发生冲突,以及处理大量文件时它的速度有多快,这是一个更好的解决方案。

    如果两个哈希值或 CRC 相同,您的反应应该完全相同:通过实际内容验证相等性。您甚至可以在 CRC 匹配后对相等大小的子集进行散列/CRC - 并检查文件大小 - 以便快速“排除”检查。


    如果您希望有许多相等的文件,散列仍然不能消除检查的需要,但它会减少需要。您仍然需要进行其他类型的检查。哈希相等,加上文件长度匹配,加上部分哈希相等(例如,对文件的第一个 x 字节进行哈希处理)可能就足够了,具体取决于您的需要。

    【讨论】:

    • 我想解释一下为什么您建议的解决方案对我来说不够好。为了获得良好的性能,我不想通过网络发送整个文件。在比较客户端之间的文件时,我希望找到许多相同的文件。您建议对于每两个具有相同哈希/CRC 的文件,我必须比较实际内容——这种情况发生得太频繁了,我将通过网络发送大量数据。哈希可能会发生冲突,但如果这种情况很少发生,以至于在我赢得彩票之前我永远不会看到这种情况发生 - 它可能已经足够好了。
    【解决方案6】:

    我遇到了单个帖子大小的限制,所以我将继续第二个帖子。

    “选择哈希函数以获得最佳性能”问题的最后说明

    关于最初的问题:“选择一个哈希函数以获得最佳性能”,我的看法如下。如果您使用 PHP,请考虑使用 MD5 来计算各种哈希和摘要。与 CRC32 相比,它在速度上几乎没有差异,至少目前在 PHP 5 和 7 中是如何实现的。一般来说,如果您使用 PHP,MD5 与 PHP 中可用的其他哈希相比在性能上有显着差异,当谈到更大的消息。 MD5的优点是生成的digest size比较小,而且速度非常快。

    如果您有 AES (AES-NI) 的硬件实现,则可以在 CBC 模式下使用 AES 来生成摘要。它将比 MD-5 快得多,具有相同的摘要大小(128 位)。

    提示:如果您需要更小的摘要但为 PHP 的文本形式,请使用 md5 二进制输出的 base64_encode,它会产生比默认使用的十六进制编码更短的结果字符串。

    从低级编程语言中使用哪些哈希函数

    如果您使用的是 Delphi 或 C++ 等编程语言,请找到一个良好的 CRC32C 实现,它可以在 Intel 和 AMD 现代处理器上通过硬件加速运行。

    正如您从我的 Delphi 测试结果中看到的,我们的代码计算了 5000 条消息的校验和,每条消息长度为 5000 字节,总体耗时仅为 0.0055349 秒。它比我们的 CRC32 的非硬件实现快了大约十倍,而后者仍然比 PHP 中实现的快得多。

    如果您需要更大的摘要,而不仅仅是 CRC32 生成的 4 个字节,而是至少 16 个字节,请考虑找到一个高性能的 MD5 实现并使用 MD5 来生成您的摘要。 MD5 是作为加密散列函数开发的,用于 PGP 加密和数字签名。它如何不再适合加密,但对于消息摘要来说仍然可以。这个散列函数不能再用于密码学,因为很容易发现冲突,但是如果你只需要将它用于你自己的校验和而不是冲突攻击的问题,我建议即使在 2017 年的今天也使用 MD5,前提是你已经找到了这个著名的散列函数的快速实现。如果您害怕冲突,并且您有 AES (AES-NI) 的硬件实现,那么请使用 AES-CBC 作为摘要。只需确保您正确实施填充即可。

    具有较大摘要的哈希可能有用的实际示例

    让我解释一下为什么有人可能需要一个具有更大摘要大小(例如 16 个字节)的哈希函数,而乍一看,您可以使用较短的一次,例如只有 4 个字节的 CRC32?

    如果更大的哈希不是问题,就不会有“选择哈希函数以获得最佳性能”之类的问题(请参阅最初的帖子)。

    假设您有一个由应用程序服务器和“memcached”服务器组成的受信任环境,其中所有应用程序都可以访问运行“memcached”恶魔的服务器上使用的所有数据,并且可以通过简单的文本键访问数据。我看到人们发明了更长的字符串,以便键在不同的领域、应用程序中是唯一的,即不重叠。因此,根据维护集群或集群组的所有程序员和管理员的惯例,他们通常同意密钥应由不同的强制段组成,如下所示: $UniqueKey = "$Namespace|$Realm|$Application| $AppComonent|$User|$Key";

    这会产生非常长的键,大约 60 个字符或更多。

    对于“memcached”守护进程,数据存储在固定大小记录(块)的slab中,它是key+value的长度组成了单个块的大小,所以96个小块的slab字节、120 字节和 192 字节几乎是空的,而真正使用的第一个平板是 304 字节块。为了避免较长密钥的这种低效率,按照惯例,您可以同意始终通过预先确定的哈希函数(如 MD5)来消化所有这些密钥。如果所有开发人员都使用相同格式的密钥,并且总是通过某个哈希函数对这些密钥进行哈希处理,那么在这个可信环境中密钥可能重叠的实际风险是不存在的。您只需对 MD 的二进制输出进行 base-64 编码并去除尾随的“=”填充,并且可以选择将 Base64 中使用的加号和减号字符替换为其他字符,例如下划线和破折号,以便获得“memcached”的漂亮列表像这样的键:

    Suj5_RxNfIq4u-36o03afg
    StRL3WgcNM6AjTSW4ozf8g
    i4Ev9nJNFpmf928PrkWbIw
    b_GE6cp9c-PT_PLwwYbDXQ
    Znci1Nj3HprfFLa0cQNi5g
    6ns__XWR7xlsvPgGwZJLBQ
    9_Yse6hFEyzgl5y5fnZaUg
    LYoIQyhNpmAHqY4r-fgZXg
    Y1fVl2rBaan0sKz-qrb8lQ
    CiLmDZwUVNW09fQaTv_qSg
    easjBIYq27dijGr2o01-5Q
    

    以不同的方式排列测试结果

    让我也以不同的方式向您展示哈希性能测试结果:除以经过的时间,因此您可以通过输出以 KB/S 看到特定的哈希:

    50000000个64字节数据块的散列

    通过通用指令在汇编中实现的 CRC32 CCITT

    • 32 位:7.2012 秒,423.7874 MB/秒
    • 64 位:7.1871s,424.6137 MB/s

    在 Delphi 中通过通用指令实现的 CRC32 CCITT

    • 32 位:7.1350 秒,427.7164 MB/秒
    • 64 位:7.3686 秒,414.1570 MB/秒

    CRC32C (RFC 3720) 通过通用指令在汇编中实现

    • 32 位:2.4866s,1227.2629 MB/s
    • 64 位:2.7694 秒,1101.9702 MB/秒

    CRC32C 通过 SSE 4.2 CRC32C 指令实现

    • 32 位:0.7099s,4298.7911 MB/s
    • 64 位:0.7510s,4063.6096 MB/s

    MD5 通过通用指令在汇编中实现

    • 32 位:4.4489 秒,685.9647 MB​​/秒
    • 64 位:4.4369s,687.8157 MB/s

    AES,通过通用指令在汇编中实现

    • 32 位:23.6519 秒,129.0280 MB/秒
    • 64 位:28.1875 秒,108.2662 MB/秒

    AES,通过 AES-NI 指令实现

    • 32 位:1.6374 秒,1863.8040 MB/秒
    • 64 位:1.6063s,1899.8995 MB/s

    我相信您已经发现哈希问题非常重要,所以它是!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-11
      • 2013-05-07
      • 2022-01-20
      • 1970-01-01
      • 2018-12-19
      • 2015-08-22
      相关资源
      最近更新 更多