哪种哈希算法最适合唯一性和速度?示例(良好)用法包括哈希字典。
我知道有SHA-256之类的东西,但是这些算法被设计为安全的,这通常意味着它们比不那么独特的算法要慢。我希望哈希算法的设计速度要快,但要保持相当独特以避免冲突。
哪种哈希算法最适合唯一性和速度?示例(良好)用法包括哈希字典。
我知道有SHA-256之类的东西,但是这些算法被设计为安全的,这通常意味着它们比不那么独特的算法要慢。我希望哈希算法的设计速度要快,但要保持相当独特以避免冲突。
Answers:
我测试了一些不同的算法,测量了速度和碰撞次数。
我使用了三种不同的键集:
"1"
来"216553"
(想邮政编码,和一个贫穷的哈希如何拿下msn.com)对于每个语料库,记录冲突次数和散列所花费的平均时间。
我测试了:
每个结果均包含平均哈希时间和冲突次数
Hash Lowercase Random UUID Numbers
============= ============= =========== ==============
Murmur 145 ns 259 ns 92 ns
6 collis 5 collis 0 collis
FNV-1a 152 ns 504 ns 86 ns
4 collis 4 collis 0 collis
FNV-1 184 ns 730 ns 92 ns
1 collis 5 collis 0 collis▪
DBJ2a 158 ns 443 ns 91 ns
5 collis 6 collis 0 collis▪▪▪
DJB2 156 ns 437 ns 93 ns
7 collis 6 collis 0 collis▪▪▪
SDBM 148 ns 484 ns 90 ns
4 collis 6 collis 0 collis**
SuperFastHash 164 ns 344 ns 118 ns
85 collis 4 collis 18742 collis
CRC32 250 ns 946 ns 130 ns
2 collis 0 collis 0 collis
LoseLose 338 ns - -
215178 collis
注意事项:
是。我开始编写测试程序以查看是否确实发生了哈希冲突-不仅仅是理论上的构造。他们确实确实发生了:
FNV-1碰撞
creamwove
与...碰撞 quists
FNV-1a碰撞
costarring
与...碰撞 liquid
declinate
与...碰撞 macallums
altarage
与...碰撞 zinke
altarages
与...碰撞 zinkes
Murmur2碰撞
cataract
与...碰撞 periti
roquette
与...碰撞 skivie
shawl
与...碰撞 stormbound
dowlases
与...碰撞 tramontane
cricketings
与...碰撞 twanger
longans
与...碰撞 whigs
DJB2碰撞
hetairas
与...碰撞 mentioner
heliotropes
与...碰撞 neurospora
depravement
与...碰撞 serafins
stylist
与...碰撞 subgenera
joyful
与...碰撞 synaphea
redescribed
与...碰撞 urites
dram
与...碰撞 vivency
DJB2a碰撞
haggadot
与...碰撞 loathsomenesses
adorablenesses
与...碰撞 rentability
playwright
与...碰撞 snush
playwrighting
与...碰撞 snushing
treponematoses
与...碰撞 waterbeds
CRC32冲突
codding
与...碰撞 gnu
exhibiters
与...碰撞 schlager
SuperFastHash冲突
dahabiah
与...碰撞 drapability
encharm
与...碰撞 enclave
grahams
与...碰撞 gramary
night
与...碰撞 vigil
nights
与...碰撞 vigils
finks
与...碰撞 vinic
另一个主观度量是散列的随机分布。映射生成的HashTables将显示数据分配的均匀程度。线性映射表时,所有哈希函数均显示良好的分布:
或作为希尔伯特地图(XKCD始终是相关的):
除了对数字字符串("1"
,,"2"
...,"216553"
)进行哈希处理(例如邮政编码)时,大多数哈希算法中都开始出现模式:
SDBM:
DJB2a:
FNV-1:
除FNV-1a以外的所有东西对我来说仍然很随机:
事实上,Murmur2似乎有更好的随机性Numbers
比FNV-1a
:
当我看
FNV-1a
“数字”图时,我认为我看到了细微的垂直图案。有了Murmur,我根本看不到任何模式。你怎么看?
*
表中的多余部分表示随机性有多糟。随着FNV-1a
是最好的,DJB2x
是最糟糕的:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
我最初编写该程序是为了确定我是否还要担心碰撞:我愿意。
然后变成确保哈希函数足够随机。
FNV1散列具有返回32、64、128、256、512和1024位散列的变体。
所述FNV-1A算法是:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
其中常数FNV_offset_basis
和FNV_prime
取决于你想要的回报散列大小:
Hash Size
===========
32-bit
prime: 2^24 + 2^8 + 0x93 = 16777619
offset: 2166136261
64-bit
prime: 2^40 + 2^8 + 0xb3 = 1099511628211
offset: 14695981039346656037
128-bit
prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
offset: 144066263297769815596495629667062367629
256-bit
prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915
有关详细信息,请参见FNV主页。
我所有的结果都是32位版本。
否。FNV-1a的状况更好。使用英文单词corpus时,与FNV-1a的冲突更多:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
现在比较小写和大写:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
在这种情况下,FNV-1a不会比FN-1差“ 400%”,仅差20%。
我认为更重要的一点是,涉及碰撞时有两种算法:
然后是哈希的分布方式:
更新资料
杂音?当然可以,为什么不
更新资料
@whatshisname想知道CRC32的性能如何,将数字添加到表中。
CRC32 很好。冲突很少,但速度较慢,并且有1k查找表的开销。
剪下有关CRC分发的所有错误信息-我的错
直到今天,我仍将FNV-1a用作我的事实上的哈希表哈希算法。但是现在我切换到Murmur2:
我真的非常希望SuperFastHash
我发现的算法有问题;太受欢迎了,真是太糟糕了。
更新:从上谷歌的MurmurHash3主页:
(1)-SuperFastHash具有非常差的碰撞属性,这在其他地方已有记录。
所以我想不仅仅是我。
更新:我意识到为什么Murmur
比其他的更快。MurmurHash2一次操作四个字节。大多数算法都是逐字节的:
for each octet in Key
AddTheOctetToTheHash
这意味着,随着按键时间的延长,Murmur将有机会发光。
更新资料
雷蒙德·陈(Raymond Chen)在及时的帖子中重申了以下事实:“随机” GUID并非旨在用于其随机性。它们或它们的子集不适合作为哈希键:
即使第4版GUID算法也不保证是不可预测的,因为该算法未指定随机数生成器的质量。Wikipedia上有关GUID的文章包含初步研究,该研究表明可以基于对随机数生成器状态的了解来预测将来和以前的GUID,因为生成器的密码学能力不强。
Randomess与避免碰撞不同;这就是为什么通过采用“随机” guid的某些子集来尝试发明自己的“哈希”算法将是一个错误的原因:
int HashKeyFromGuid(Guid type4uuid)
{
//A "4" is put somewhere in the GUID.
//I can't remember exactly where, but it doesn't matter for
//the illustrative purposes of this pseudocode
int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
Assert(guidVersion == 4);
return (int)GetFirstFourBytesOfGuid(type4uuid);
}
注意:再次,我在引号中加上了“ random GUID”,因为它是GUID的“ random”变体。更准确的描述是Type 4 UUID
。但是没人知道4型是什么,或者1、3和5型是什么。因此,将它们称为“随机” GUID会更容易。
如果您希望通过不变的字典创建哈希映射,则可以考虑使用完美的哈希https://en.wikipedia.org/wiki/Perfect_hash_function-在构造哈希函数和哈希表期间,您可以保证,对于给定的数据集,不会有冲突。
这是哈希函数的列表,但简短的版本是:
如果您只是想拥有一个好的哈希函数,并且迫不及待,那
djb2
是我所知道的最好的字符串哈希函数之一。它在许多不同的键和表大小集上具有出色的分布和速度
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
Google的CityHash是您要寻找的算法。它对密码术不利,但对生成唯一的哈希值则有利。
CityHash用C ++编写。还有一个普通的C端口。
所有CityHash功能都针对64位处理器进行了调整。也就是说,它们将以32位代码运行(使用SSE4.2的新版本除外)。他们不会很快。您可能要使用Murmur或其他32位代码。
plain C port
链接已断开
在对文件进行哈希处理时,我对不同的哈希算法进行了简短的速度比较。
由于所有文件都存储在tmpfs中,因此各个图在读取方法上仅稍有不同,在此处可以忽略。因此,如果您想知道基准测试不受IO限制。
算法包括:SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}
。
结论:
CRC
指令的CPU上,Cityhash可能更快,而我的CPU没有。就我而言,SpookyHash总是比CityHash靠前一点。用于绘图的来源:
SHA算法(包括SHA-256)被设计为快速的。
实际上,有时它们的速度可能会成为问题。特别地,用于存储源自密码的令牌的常用技术是运行标准快速哈希算法10,000次(将哈希值的哈希值存储在...密码的哈希值中)。
#!/usr/bin/env ruby
require 'securerandom'
require 'digest'
require 'benchmark'
def run_random_digest(digest, count)
v = SecureRandom.random_bytes(digest.block_length)
count.times { v = digest.digest(v) }
v
end
Benchmark.bmbm do |x|
x.report { run_random_digest(Digest::SHA256.new, 1_000_000) }
end
输出:
Rehearsal ------------------------------------
1.480000 0.000000 1.480000 ( 1.391229)
--------------------------- total: 1.480000sec
user system total real
1.400000 0.000000 1.400000 ( 1.382016)
bcrypt
。使用正确的工具。
.rodata
和/或状态成本。当您需要用于哈希表的算法时,通常您会拥有非常短的密钥,并且其中有很多密钥,但是不需要额外的保证。我自己一次调整了詹金斯的作品。
我知道有SHA-256之类的东西,但是这些算法被设计为安全的,这通常意味着它们比不那么独特的算法要慢。
密码散列函数更独特的假设是错误的,实际上,在实践中可以证明它经常倒退。事实上:
这意味着,与用于“良好”数据集(为它设计的数据集)相比,非加密哈希函数的冲突可能要少得多。
我们实际上可以用Ian Boyd的答案中的数据和一些数学运算来证明这一点:Birthday问题。如果n
从集合中随机选择整数,则期望的碰撞对数的公式[1, d]
是(来自维基百科):
n - d + d * ((d - 1) / d)^n
堵塞n
= 216,553和d
= 2 ^ 32,我们得到约5.5个预期的碰撞。伊恩(Ian)的测试大部分显示了该邻域周围的结果,但有一个戏剧性的例外:大多数函数在连续数字测试中的碰撞次数为零。随机选择216,553个32位数字并获得零冲突的可能性约为0.43%。这仅是一个函数,这里我们有五个零碰撞的不同哈希函数系列!
因此,我们在这里看到的是,Ian测试的哈希与连续数字数据集交互良好,即,与理想的密码散列函数相比,它们分散的最小差异输入更为广泛。(旁注:这意味着Ian可以从他自己的数据中驳斥Ian的图形评估,即FNV-1a和MurmurHash2在number数据集中对他“看起来是随机的”。对于该大小的数据集,对于两个哈希函数,零冲突,绝对是非随机的!)
这并不奇怪,因为对于哈希函数的许多使用,这是理想的行为。例如,哈希表键通常非常相似。伊恩(Ian)的答案提到了MSN曾经存在的邮政编码哈希表问题。这是在避免可能输入上的冲突胜过类似随机行为的情况下使用的。
这里的另一个有益的比较是CRC和加密散列函数在设计目标上的对比:
因此,对于CRC来说,在最小限度的不同输入中具有比随机发生的冲突少的冲突也是一件好事。使用加密哈希,这是一个禁忌!
使用SipHash。它具有许多理想的属性:
快速。 一个优化的实现每个字节大约需要1个周期。
安全。 SipHash是强大的PRF(伪随机函数)。这意味着它与随机函数是没有区别的(除非您知道128位密钥)。因此:
无需担心哈希表探针由于冲突而变成线性时间。使用SipHash,您知道无论输入多少,都将获得平均情况下的平均性能。
不受基于散列的拒绝服务攻击的影响。
您可以将SipHash(尤其是具有128位输出的版本)用作MAC(消息身份验证代码)。如果您收到一条消息和一个SipHash标记,并且该标记与使用您的秘密密钥运行SipHash的标记相同,则您知道创建哈希的任何人也都拥有您的秘密密钥,并且消息和密码都没有。此后,哈希值已更改。
Java使用以下简单的乘加算法:
字符串对象的哈希码计算为
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用INT算术,其中
s[i]
是我的字符串的第字符,n
是字符串的长度,以及^
表示取幂。(空字符串的哈希值为零。)
可能有更好的选择,但这是相当普遍的,并且似乎在速度和唯一性之间进行了很好的权衡。
首先,为什么需要实现自己的哈希?对于大多数任务,假设有可用的实现,您应该使用标准库中的数据结构获得良好的结果(除非您只是为了自己的学习而这样做)。
就实际的哈希算法而言,我个人最喜欢的是FNV。1个
这是C语言中32位版本的示例实现:
unsigned long int FNV_hash(void* dataToHash, unsigned long int length)
{
unsigned char* p = (unsigned char *) dataToHash;
unsigned long int h = 2166136261UL;
unsigned long int i;
for(i = 0; i < length; i++)
h = (h * 16777619) ^ p[i] ;
return h;
}
*
和^
:h = (h * 16777619) ^ p[i]
==>h = (h ^ p[i]) * 16777619