Ami*_*nen 8 .net hash .net-4.0
我需要在网络上比较许多文件(有些可能很大,有些可能很小).因此,我计划对每个客户端上的每个文件进行哈希处理,并仅通过网络发送哈希值.
这里的主要目标是表现.这意味着网络流量最小.安全不是问题.
还应该有"零"碰撞,因为我不想错误地认为两个不同的文件是相同的.这么说,我知道理论上总会发生碰撞,我只是希望实际上遇到它们的机会绝对可以忽略不计.
所以我的问题是:哪个.net哈希函数最适合这个任务?
我正在考虑使用缓冲读卡器MD5CryptoServiceProvider(因为CNG可能并非在所有客户端都可用).
有没有办法获得比这更好的表现?(也许使用一些外部库?)
Mic*_*ael 19
我遇到了类似的情况,我需要一个 .NET 散列算法。我需要它用于服务器响应缓存,其中速度比安全性更重要。当我进入这个线程时,我注意到关于算法选择和 32 位与 64 位执行的性能差异的猜测。为了将一些科学带入这场辩论,我创建了一些代码来实际测试一些可用的算法。我决定测试内置的 MD5、SHA1、SHA256 和 SHA512 算法。我还包括从CRC32执行力网,并从CRC64实现DamienGKit。我对 ~115MB 文件的结果如下:
在 32 位模式下运行。
热身阶段:
CRC32:390 毫秒内为 296 MiB/s [9C54580A]。
CRC64:95 MiB/s [636BCF1455BC885A] 1212ms。
MD5:191 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==] 在 604 毫秒内。
SHA1:165 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 699 毫秒内。
SHA256:93 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1240 毫秒内。
SHA512:2464 毫秒内为 47 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...]。
最后运行:
CRC32:414 毫秒内为 279 MiB/s [9C54580A]。
CRC64:1203 毫秒内为 96 MiB/s [636BCF1455BC885A]。
MD5:197 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==] 588ms。
SHA1:164 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 707 毫秒内。
SHA256:96 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1200 毫秒内。
SHA512:2441 毫秒内为 47 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...]。
在 64 位模式下运行。
热身阶段:
CRC32:373 毫秒内为 310 MiB/s [9C54580A]。
CRC64:986 毫秒内为 117 MiB/s [636BCF1455BC885A]。
MD5:198 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==] 在 584 毫秒内。
SHA1:627 毫秒内 184 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...]。
SHA256:1112 毫秒内为 104 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...]。
SHA512:778 毫秒内为 149 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...]。
最后运行:
CRC32:396 毫秒内为 292 MiB/s [9C54580A]。
CRC64:975 毫秒内为 119 MiB/s [636BCF1455BC885A]。
MD5:199 MiB/s [mm/JVFusWMKcT/P+IR4BjQ==] 在 582 毫秒内。
SHA1:192 MiB/s [WSFkGbnYte5EXb7kgp1kqbi2...] 在 601 毫秒内。
SHA256:106 MiB/s [USKMHQmfMil8/KL/ASyE6rm/...] 在 1091 毫秒内。
SHA512:738 毫秒内为 157 MiB/s [Cp9cazN7WsydTPn+k4Xu359M...]。
这些结果是从运行 .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 并没有性能优势。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 />";
Run Code Online (Sandbox Code Playgroud)
这取决于您拥有的文件数量.
碰撞的可能性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在合理的时间内仍然可以获得尽可能接近的消息.
除了 Michael 之前在这里进行的分析(参见上面的帖子)之外,我将进一步分析内置 PHP 哈希的性能,因为这个主题非常有趣并且有意想不到的结果。
结果并不那么明显,甚至并不令人惊讶。一种简单的算法——CRC32 或 CRC16,比复杂的算法——MD5 慢。似乎现代 CPU 不喜欢特定的旧算法并且执行它们非常缓慢,至少当算法没有以新方式实现时,利用现代 CPU 架构。CRC16 CCITT 算法在过去有 300 BPS 拨号调制解调器时相对快速和高效。现在有专门为新硬件设计的现代算法,它们在相同硬件上的运行速度可能比旧算法快得多,旧算法本质上不适合新硬件,即使您尝试优化它们,它们无论如何也会相对较慢。例如,
您可能会从其他密码库中看到证实了我们在 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;
Run Code Online (Sandbox Code Playgroud)
这是汇编上的代码:
@calc_crc32:
xor dl,[esi]
mov al,dl
shr edx,8
xor edx,dword ptr [edi+eax*4]
inc esi
loop @calc_crc32
Run Code Online (Sandbox Code Playgroud)
您也可以展开此代码,因此每个字节只会获得 5 个 CPU 指令:
xor dl,bl
shr rbx,8
mov al,dl
shr edx,8
xor edx,dword ptr [r8+rax*4]
Run Code Online (Sandbox Code Playgroud)
您只需要将rbx接下来的 8 个字节的数据加载到寄存器中,然后重复此代码 8 次,直到您需要将接下来的 8 个字节加载到rbx64 位寄存器。
这是计算整个字符串的 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;
Run Code Online (Sandbox Code Playgroud)
以下是 Delphi 将其编译为机器代码的方式——不是非常理想,但非常简单——每个字节只有 11 条汇编指令,令人惊讶的是,即使在英特尔酷睿 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
Run Code Online (Sandbox Code Playgroud)
这是CRC32汇编代码的另一种变体,每个字节只有5个处理器命令,而不是11个,但它与上面的汇编代码基本相同,只是使用了不同的寄存器并避免了再次出现在i5上的“循环”命令- 6600 比两种不同的指令快。您可以在从 C 控制台应用程序调用的 CRC32 汇编程序函数中找到整个代码
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
Run Code Online (Sandbox Code Playgroud)
现在将它与 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
Run Code Online (Sandbox Code Playgroud)
您可以在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;
Run Code Online (Sandbox Code Playgroud)
如果您的处理器支持 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;
Run Code Online (Sandbox Code Playgroud)
请注意,在我的示例中,我仅使用了 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 处理器上自古以来就受支持。Intel 从 2008 年 11 月开始支持 CRC32(Nehalem(微架构)),而 AMD 似乎从 2013 年开始支持它。
我已经在不同的配置上测试了各种 PHP 哈希函数:(1)AMD FX-8320(2012 年发布)在 Ubuntu 下使用 PHP 5,以及(2)Intel Core i5-6600 在 2015 年发布在 Windows 下使用 PHP 7。我也有在此英特尔酷睿 i5-6600 上运行 OpenSSL 测试。除此之外,我还对我们在“The Bat!”软件中使用的加密程序进行了测试。用德尔福写的。虽然主要软件是用 Delphi 编写的,但我们使用的加密例程是用 Assembler for Intel 处理器(32 位或 64 位)或 C 编写的。
我发现我们的 Delphi 代码在各种散列函数和数据大小之间显示出非常大的速度差异。这与 PHP 形成对比,在 PHP 中,从最简单的 CRC32 到曾经加密强大的 MD5,在一定程度上,除了极少数例外,所有散列函数都具有几乎相同的输出速度。
所以,这里是我在 AMD FX-8320、PHP5、Ubuntu 上所做的测量。我做了两个测试用例。首先,我运行了 5000 次迭代来散列仅包含 5 个字节的消息。通过这个小的消息大小,我打算测试各种算法的初始化/完成步骤的持续时间,以及它如何影响整体性能。对于某些算法,如 CRC32,三个实际上没有完成步骤 - 摘要总是在每个字节之后准备好。像 SHA1 或 MD5 或其他加密强函数有一个终结步骤,将较大的上下文压缩为较小的最终摘要。其次,我运行 5000 次迭代来散列一条 5000 字节长的消息。两个消息都预先用伪随机字节填充(每次迭代后它们不会重新填充;它们只填充一次,当程序启动时)。
我已经修改了您的 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
Run Code Online (Sandbox Code Playgroud)
然后我在英特尔酷睿 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
Run Code Online (Sandbox Code Playgroud)
如您所见,在 PHP 中计算消息的 CRC32,在我几乎所有的测试中,计算相同消息的 MD5 所需的时间仅为其一半左右。唯一的例外是在英特尔酷睿 i5-6600 上使用 PHP7 测试 5000 条 5000 字节的消息时,CRC32 甚至比 MD5 花费的时间更长(!)。这个奇怪的结果在我的案例中总是可以重复的。我找不到合理的解释。
此外,在 PHP 上,MD5 和 SHA1 之间几乎没有明显的速度差异,除了在带有 PHP5 的 Ubuntu 上,在测试 5000 条 5000 字节的消息时,MD5 快了两倍。
以下是 Intel 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
Run Code Online (Sandbox Code Playgroud)
同样,md5 和 sha1 之间几乎没有差异,这很奇怪,需要进一步调查 MD5 和 SHA-1 算法在时间消耗方面是否本质上相同。
以下是我们的 Delphi 库在 Windows 10 64 位下 Intel Core i5-6600 上的结果,测试的代码是 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
Run Code Online (Sandbox Code Playgroud)
如您所见,MD2 也比其他散列要大得多——结果与 PHP 代码相同,但 MD5 比 SHA-1 快得多,总体而言,在 Delphi 中在同一台机器上执行相同操作所需的时间更少以 PHP 为例,PHP7 用 0.001284 秒消化 5000 条 MD5 5 字节消息,用 SHA1 消化 0.001499 秒。作为大约 5000 字节的消息 - PHP7 使用 MD5 需要 0.046208 秒,使用 SHA-1 需要 0.050259 秒。
至于 Delphi,用 MD5 消化 5000 条 5 字节消息需要 0.0005015 秒,用 SHA1 消化 0.0050118 秒。作为大约 5000 字节的消息 - Delphi 使用 MD5 花费了 0.0376252 秒,使用 SHA-1 花费了 0.1830871 秒。如您所见,MD5 在 Delphi 中运行得更快,但 SHA-1 大致相同。此外,Delphi 在 5 字节消息上的速度大约快 10 倍,但对于大约 5000 字节的消息,它与 SHA-1 大致相同甚至更慢。
但是说到CRC