选择哈希函数以获得最佳性能

时间:2012-04-09 07:34:00

标签: .net .net-4.0 hash

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

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

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

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

6 个答案:

答案 0 :(得分:8)

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

碰撞的可能性P(collision) = c/2^N(在完美的散列函数中),其中c是您的消息数(文件)和N是碰撞算法中的位数

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

在第一种情况下,您需要使用CRC32。 CRC32非常常见,但是,根据您拥有的文件数量,可能还不够:您可以保证在大约4,3亿条消息(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,同时仍然在合理的时间内运行。

答案 1 :(得分:4)

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

您应该考虑使用CRC或其他校验和功能;有一个常用的here列表。 HashLib已经做好了现成的实施。

答案 2 :(得分:4)

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

  

以32位模式运行

     

预热阶段

     

CRC32:296 MiB / s [9C54580A],390ms。

     

CRC64:95 MiB / s [636BCF1455BC885A],1212ms。

     

MD5:在214ms内191 MiB / s [mm / JVFusWMKcT / P + IR4BjQ ==]。

     

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

     

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

     

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

     

最终运行

     

CRC32:279 MiB / s [9C54580A],414ms。

     

CRC64:96 MiB / s [636BCF1455BC885A],1203ms。

     

MD5:在588ms内197 MiB / s [mm / JVFusWMKcT / P + IR4BjQ ==]。

     

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

     

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

     

SHA512:47 MiB / s [Cp9cazN7WsydTPn + k4Xu359M ...],2441ms。

           

以64位模式运行

     

预热阶段

     

CRC32:310 MiB / s [9C54580A],373ms。

     

CRC64:117 MiB / s [636BCF1455BC885A],986ms。

     

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

     

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

     

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

     

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

     

最终运行

     

CRC32:292毫秒,292 MiB / s [9C54580A]。

     

CRC64:119 MiB / s [636BCF1455BC885A],975ms。

     

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

     

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

     

SHA256:106 MiB / s [USKMHQmfMil8 / KL / ASyE6rm / ...],1091ms。

     

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

这些结果是从运行.NET v4.5.2的已编译的Release-build 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)是最快的,其次是微软的MD5和SHA1。奇怪的是,在内置MD5或SHA1上选择DamienGKit的CRC64没有性能优势。对于SHA512,64位执行似乎有很大帮助,但只能与其他人一起适度。

要回答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 />";

答案 3 :(得分:3)

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

快速“这些相同”检查。哈希不能告诉你两件事是否相等,因为他们发生碰撞。

因此,鉴于一个简单的CRC很少会发生冲突,以及它对大量文件的速度有多快,这是一个更好的解决方案。

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


如果你希望有许多相同的文件,哈希仍然不会消除检查的必要,但它会减少需要。你还是想做其他检查。根据您的需要,散列等式,加上文件长度匹配以及部分散列等式(例如散列文件的第一个x字节)可能已经足够了。

答案 4 :(得分:2)

测试结果分析

我将进一步分析内置PHP哈希的性能,除了[迈克尔] [1]之前的分析(参见上面的帖子),因为这个主题非常有趣并且有意想不到的结果。

结果并不那么明显,甚至令人惊讶。一个简单的算法 - CRC32,比复杂的算法慢 - MD5。似乎现代CPU不喜欢特定的旧算法并且执行速度非常慢。 CRC32 CCIT ITU算法在有300台BPS拨号调制解调器的好时光中相对快速和高效。现在有专门为新硬件设计的现代算法,它可以在相同的硬件上比旧算法更快地工作,旧算法固有地不适合新硬件,即使你试图优化它们,它们也会很慢。例如,对于每个字节取决于先前再见的算法,您无法利用64位寄存器并并行处理多个位。

您可以从其他加密库中看到确认我们在PHP中看到的内容 -  CRC32与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;

以下是关于Assembly的代码:

@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个汇编指令,令人惊讶的是,英特尔酷睿i5-6600的工作速度比上面的汇编程序快一点循环展开后甚至代码。如您所见,实现CRC32 CCIT ITU的所有这些指令都是直接的,没有循环或比较,每个字节的末尾只有一个比较。这只是编译的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个,但它与上面的汇编程序代码基本相同,只是使用不同的寄存器并避免使用&#34;循环&#34 ;命令再次在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

上述代码在一次调用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 dword ptr 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 dword ptr eax,dword ptr [rdx]
        crc32 dword ptr eax,dword ptr [rdx+4]
        dec r8
        lea rdx,rdx+8
        jnz @1
 @2:    and rcx,7; jz @0
        cmp rcx,4; jb @4
        crc32 dword ptr eax,dword ptr [rdx]
        sub rcx,4
        lea rdx,rdx+4
        jz @0
@4:     crc32 dword ptr eax,byte ptr [rdx]
        dec rcx; jz @0
        crc32 dword ptr eax,byte ptr [rdx+1]
        dec rcx; jz @0
        crc32 dword ptr eax,byte ptr [rdx+2]
        @0: not eax
        end;

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

在PHP中,即使在版本7中,似乎也不支持CRC32的硬件加速,尽管这些指令在很长一段时间内都受到英特尔和AMD处理器的支持。自2008年11月以来,英特尔支持CRC32(Nehalem(微架构)),AMD似乎从2013年开始支持它。

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

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

我发现我们的Delphi代码显示了各种散列函数和数据大小之间非常大的速度差异。这与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。唯一的例外是,在使用PHP7的英特尔酷睿i5-6600上测试5000个5000字节的消息时,CRC32甚至比MD5(!)花费的时间更长。这个奇怪的结果总是可以重复我的情况。我无法找到合理的解释。

此外,在PHP上,MD5和SHA1之间几乎没有明显的速度差异,除了使用PHP5的Ubuntu,其中5000个5000字节的消息测试,MD5快两倍。

我的OpenSSL哈希性能测试结果

以下是英特尔i5-660上OpenSSL的测试。他们以不同的方式展示它们没有显示消化某些数据集所需的时间,反之亦然:它们显示了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 Hash函数性能

的结果

以下是我们在Windows 10 64位下的Intel Core i5-6600上的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用MD12消耗5000个5字节消息0.00184秒,用SHA1消化0.001499秒。大约5000个字节的消息 - MD5为0.046208秒,SHA-1为0.050259秒。

关于Delphi,使用MD5消耗5000个5字节消息和使用SHA1消耗0.0050118秒需要0.0005015秒。大约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,最初被设计为加密散列函数,然后由PGP在RFC-1991中使用,现在不能再用于密码术,但可以用作可信环境中的校验和,例如用于ETag或其他方式。正确实现此功能非常快(并且在PHP上它至少不慢),与其他功能相比,MD5产生非常紧凑的摘要。这是我的PHP代码,它们构成了这些基准。我是根据迈克尔的原始代码示例制作的(见上文)。

<?
 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();
 }
?>

答案 5 :(得分:0)

我遇到了一个帖子大小的限制,所以我会在第二篇文章中继续。

最终注意事项&#34;选择哈希函数以获得最佳性能&#34;问题

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

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

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

从低级编程语言

中使用哪个散列函数

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

从我的Delphi测试结果中可以看出,我们的代码计算了5000条消息的校验和,每条长度为5000字节,总体上只需0.0055349秒。它比CRC32的非硬件实现速度快十倍,而CRC32的反转速度仍然远远快于PHP实现的速度。

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

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

让我解释一下为什么有人可能需要一个具有更大摘要大小的散列函数,比如16个字节,乍一看,你可以使用更短的一次,比如CRC32只有4个字节?

如果较大的哈希值不是问题,那么就不会出现像#34;选择哈希函数以获得最佳性能的问题&#34; (见最初的帖子)。

考虑到您拥有一个受信任的应用程序服务器环境,并且&#34; memcached&#34;服务器,其中所有应用程序都可以访问运行&#34; memcached&#34;的服务器上使用的所有数据。恶魔,数据通过简单的文本键访问。我看到人们发明了更长的字符串,因为密钥在不同的领域,应用程序中是唯一的,即不重叠。因此,他们通常同意,通过维护集群或一组集群的所有程序员和管理员的约定,密钥应包含不同的强制段,如下所示:$ UniqueKey =&#34; $ Namespace | $ Realm | $应用| $ AppComonent | $用户| $密钥&#34 ;;

这是非常长的按键,大约60个字符或更多。

对于&#34; memcached&#34;守护进程,数据存储在固定大小的记录(块)的板中,并且它是键+值的长度,构成单个块的大小,因此96字节,120字节和192字节的小块的板是几乎是空的,而真正使用的第一块板是304字节的块。为了避免较长密钥的这种低效率,按照惯例,您可以同意通过预定的散列函数(如MD5)来消化所有这些密钥。如果所有开发人员将使用相同格式的密钥并且将始终通过某个散列函数散列这些密钥,则密钥可能在此受信任环境中重叠的实际风险。您只需对MD的二进制输出进行base-64编码并去掉尾随的“=”填充,并且可选地将Base64中使用的加号和减号替换为下划线和短划线等其他字符,这样就可以获得很好的列表#34 ; memcached的&#34;像这样的键:

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

以不同方式布置测试结果

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

  • CRC32 CCIT基准测试,32位汇编程序:6400个50000000个数据块 CRC32处理的字节数为7.2012秒;每个423.7874兆字节 第二;
  • CRC32 CCIT基准测试,64位汇编程序:6400个50000000个数据块 CRC32处理的字节数为7.1871秒; 424.6137兆字节每 第二;
  • CRC32 CCIT基准测试,32位,纯Delphi:6400的50000000数据块 由CRC32处理的字节需要7.1350秒;每个427.7164兆字节 第二;
  • CRC32 CCIT基准测试,64位,纯Delphi:6400的50000000个数据块 CRC32处理的字节数为7.3686秒; 414.1570兆字节每秒;
  • CRC32C软件实现基准测试,32位位汇编程序: 5000个64字节的数据块需要2.4866秒来处理 CRC32C; 1227.2629每秒兆字节;
  • CRC32C软件实现基准测试,64位汇编程序:50000000 由CRC32C处理64字节的数据块需要2.7694秒; 每秒1101.9702兆字节;
  • CRC32C硬件实现基准,32位,SSE 4.2:50000000 64字节的数据块由CRC32C处理0.7099秒 使用SSE 4.2 CPU命令; 4298.7911每秒兆字节;
  • CRC32C硬件实现基准,32位,SSE 4.2:50000000 64字节的数据块花费0.7510秒由CRC32使用 SSE 4.2 CPU命令; 4063.6096每秒兆字节;
  • MD5基准测试,32位,汇编程序:50000000个64字节数据块 用MD5处理4.4489秒; 685.9647兆字节/秒
  • MD5基准测试,64位,汇编程序:6400字节的50000000个数据块; 用MD5处理4.4369秒; 687.8157每秒兆字节;
  • AES基准测试,软件实现,32位,汇编程序:50000000 64字节的数据块在AES-CBC中处理需要23.6519秒 使用128位密钥; 129.0280兆字节每秒;
  • AES基准测试,软件实现,64位,汇编程序:50000000 64字节的数据块在AES-CBC中处理28.1875秒 使用128位密钥;每秒108.2662兆字节;
  • AES硬件实施基准测试,64位,AES-NI:50000000数据 64字节的块需要1.6374秒才能在AES-CBC中处理 128位密钥; 1863.8040每秒兆字节;
  • AES基准测试,硬件实现,64位,AES-NI:50000000数据 64字节的块需要1.6063秒才能在AES-CBC中处理 128位密钥; 1899.8995每秒兆字节。

我确定你发现哈希问题非常重要,所以它就是!