it-swarm.cn

哪种哈希算法最适合唯一性和速度?

哪种哈希算法最适合唯一性和速度?示例(良好)用法包括哈希字典。

我知道有类似 SHA-256 之类的东西,但是这些算法是designedsecure,通常表示它们比nique较小的算法要慢。我希望哈希算法的设计速度要快,但要保持相当独特以避免冲突。

1444
Earlz

我测试了一些不同的算法,测量了速度和碰撞次数。

我使用了三种不同的键集:

对于每个语料库,记录冲突次数和散列所花费的平均时间。

我测试了:

结果

每个结果均包含平均哈希时间和冲突次数

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

注意

  • LoseLose算法 (其中hash = hash + character)实际上是awful。一切都碰撞到相同的1,375个桶中
  • SuperFastHash速度很快,看起来很分散。根据我的天赋number发生冲突。我希望 移植它的人出了点问题;这很糟糕
  • CRC32是非常好。速度较慢,还有1k的查询表

碰撞真的发生了吗?

是。我开始编写测试程序,以查看是否发生actually哈希冲突-不仅仅是理论上的构造。他们确实确实发生了:

FNV-1碰撞

  • creamwovequists发生冲突

FNV-1a碰撞

  • costarringliquid发生冲突
  • declinatemacallums发生冲突
  • altaragezinke发生冲突
  • altarageszinkes发生冲突

Murmur2碰撞

  • cataractperiti发生冲突
  • roquetteskivie发生冲突
  • shawlstormbound发生冲突
  • dowlasestramontane发生冲突
  • cricketingstwanger发生冲突
  • longanswhigs发生冲突

DJB2碰撞

  • hetairasmentioner发生冲突
  • heliotropesneurospora发生冲突
  • depravementserafins发生冲突
  • stylistsubgenera发生冲突
  • joyfulsynaphea发生冲突
  • redescribedurites发生冲突
  • dramvivency发生冲突

DJB2a碰撞

  • haggadotloathsomenesses发生冲突
  • adorablenessesrentability发生冲突
  • playwrightsnush发生冲突
  • playwrightingsnushing发生冲突
  • treponematoseswaterbeds发生冲突

CRC32冲突

  • coddinggnu发生冲突
  • exhibitersschlager发生冲突

SuperFastHash冲突

  • dahabiahdrapability发生冲突
  • encharmenclave发生冲突
  • grahamsgramary发生冲突
  • ...剪下79次碰撞...
  • nightvigil发生冲突
  • nightsvigils发生冲突
  • finksvinic发生冲突

随机化

另一个主观度量是散列的随机分布。映射生成的HashTables将显示数据分配的均匀程度。线性映射表时,所有哈希函数均显示良好的分布:

Enter image description here

或作为 Hilbert MapXKCD始终是相关的 ):

Enter image description here

散列数字字符串("1""2",...,"216553")时(例如 邮政编码 )除外,其中模式开始出现在大多数哈希算法中:

[〜#〜] sdbm [〜#〜]

Enter image description here

DJB2a

Enter image description here

FNV-1

Enter image description here

FNV-1a之外的所有东西,对我来说仍然很随机:

Enter image description here

实际上,[Murmur2似乎比FNV-1a具有更好的Numbers随机性:

Enter image description here

当我查看FNV-1a“数字”映射时,我认为我看到了细微的垂直图案。有了Murmur,我根本看不到任何模式。你觉得呢?


表中多余的*表示随机性有多糟糕。 FNV-1a是最好的,而DJB2x是最差的:

      Murmur2: .
       FNV-1a: .
        FNV-1: ▪
         DJB2: ▪▪
        DJB2a: ▪▪
         SDBM: ▪▪▪
SuperFastHash: .
          CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
     Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
                                        ▪
                                 ▪▪▪▪▪▪▪▪▪▪▪▪▪
                        ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
          ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

我最初编写该程序是为了决定是否甚至需要worry关于碰撞:我愿意。

然后变成确保哈希函数足够随机。

FNV-1a算法

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_basisFNV_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-1比FNV-1a好吗?

否。FNV-1a的状况更好。使用英语单词语料库时,与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%。

我认为更重要的一点是,涉及碰撞时有两种算法:

  • 罕见冲突:FNV-1,FNV-1a,DJB2,DJB2a,SDBM
  • 常见冲突:SuperFastHash,Loselose

然后是哈希的分布方式:

  • 杰出分布: Murmur2,FNV-1a,SuperFastHas
  • 优秀的发行版: FNV-1
  • 良好分配: SDBM,DJB2,DJB2a
  • 可怕的分布: Loselose

更新

杂音?当然,为什么不


更新

@whatshisname想知道CRC32如何执行,将数字添加到表中。

CRC32是非常好。冲突很少,但速度较慢,并且有1k查找表的开销。

截断有关CRC分发的所有错误信息-我的错


直到今天,我仍将FNV-1a用作我的de facto哈希表哈希算法。但是现在我切换到Murmur2:

  • 快点
  • 改善所有输入类别的randomnessification

我真的,really希望我发现的 SuperFastHash算法有问题 ;太受欢迎了,真是太糟糕了。

更新:来自 Google 上的MurmurHash3主页:

(1)-SuperFastHash具有非常差的碰撞属性,这在其他地方已有记录。

所以我想不仅仅是我。

更新:我意识到了Murmur比其他更快。 MurmurHash2一次操作四个字节。多数算法是个字节

for each octet in Key
   AddTheOctetToTheHash

这意味着,随着按键时间的延长,Murmur将获得闪耀的机会。


更新

GUID设计为唯一而非随机

Raymond Chen在及时的帖子中重申了“ random”GUID并非旨在用于其随机性的事实。它们或它们的子集不适合作为哈希键:

即使版本4的GUID算法也不保证是不可预测的,因为该算法未指定随机数生成器的质量。 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会更容易。

所有英文单词的镜像

2530
Ian Boyd

如果您希望通过不变的字典创建哈希映射,则可能需要在哈希函数的构造过程中考虑完美的哈希 https://en.wikipedia.org/wiki/Perfect_hash_function -哈希表,对于给定的数据集,您可以保证不会发生冲突。

61
Damien

这里 是哈希函数的列表,但简短的版本是:

如果您只想拥有良好的哈希函数,并且迫不及待,则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;
}
34
Dean Harding

Google的CityHash是您要寻找的算法。它对密码术不利,但对生成唯一的哈希值则有利。

阅读 blog 了解更多详细信息,以及 代码可在此处获得

CityHash用C++编写。还有一个 普通C端口

大约32位支持:

所有CityHash功能都针对64位处理器进行了调整。也就是说,它们将以32位代码运行(使用SSE4.2的新版本除外)。他们不会很快。您可能要使用Murmur或其他32位代码。

29
Vipin Parakkat

在对文件进行哈希处理时,我对不同的哈希算法进行了简短的速度比较。

由于所有文件都存储在tmpfs中,因此各个图在读取方法上仅稍有不同,在此处可以忽略。因此,如果您想知道基准测试不受IO限制。

算法包括:SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}

结论:

  • 诸如Murmur3,Cityhash和Spooky之类的非加密哈希函数非常接近。应该注意的是,使用SSE 4.2s CRC指令,但我的CPU没有此指令),Cityhash在CPU上可能更快。
  • 尽管SHA256对于MD5和SHA1的 冲突漏洞 可能更安全,但使用加密哈希函数时,MD5似乎是一个不错的折衷。
  • 所有算法的复杂度都是线性的-这并不奇怪,因为它们是逐块工作的。 (我想看看读取方法是否有所不同,所以您可以比较最右边的值)。
  • SHA256比SHA512慢。
  • 我没有研究哈希函数的随机性。但是 这里Ian Boyds答案 中缺少的哈希函数的很好的比较。这指出CityHash在极端情况下存在一些问题。

用于绘图的来源:

21
Sahib

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)
18
yfeldblum

我知道有类似SHA-256之类的东西,但是这些算法是设计的安全的,这通常意味着它们比nique)少的算法要慢。

密码散列函数更独特的假设是错误的,实际上,在实践中可以证明它经常倒退。事实上:

  1. 理想情况下,密码散列函数应为与随机无区别;
  2. 但是,对于非密码哈希函数,希望它们与可能的输入进行良好交互

这意味着,与用于“好的”数据集的加密密码散列函数相比,非加密的哈希函数很可能具有更少的冲突

我们实际上可以用Ian Boyd的答案中的数据和一些数学运算来证明这一点: 生日问题 。如果从集合[1, d]中随机选择n整数,则碰撞对的预期数目的公式是这样的(摘自Wikipedia):

n - d + d * ((d - 1) / d)^n

插入n216,553和d = 2 ^ 32,我们得到大约5.5个预期的碰撞。伊恩(Ian)的测试大部分显示了该邻域附近的结果,但有一个戏剧性的例外:在连续数字测试中,大多数函数都具有零碰撞。随机选择216,553个32位数字并获得零冲突的可能性约为0.43%。这仅是一个函数-在这里,我们有五个不同的哈希函数族且零碰撞!

因此,我们在这里看到的是,Ian测试的哈希与连续的数字数据集正在交互有利地,即,它们分散最小地不同的输入的散布比理想的密码散列函数要广泛得多。 (旁注:这意味着Ian可以从他自己的数据中驳斥Ian的图形评估,即FNV-1a和MurmurHash2在数字数据集中对他“看起来是随机的”。对该大小的数据集的零冲突,对于散列函数,显然是非随机的!)

这不足为奇,因为这对于哈希函数的许多使用来说是理想的行为。例如,哈希表键通常非常相似。伊恩(Ian)的答案提到_ = MSN曾经有一个邮政编码哈希表 的问题。这是在可能输入上的冲突避免胜过类似随机行为的情况下使用的情况。

这里的另一个有益的比较是CRC和加密散列函数在设计目标上的对比:

  • CRC旨在捕获由于通信信道嘈杂而导致的错误,这可能是少量的位翻转;
  • 加密散列的目的是捕获由恶意攻击者进行的修改,它们分配了有限的计算资源,但随意性很高。

因此,对于CRC来说,在最小限度不同的输入中具有比随机数少的冲突也是。对于加密散列,这是一个禁忌!

15
sacundim

使用 SipHash 。它具有许多理想的特性:

  • 快速优化的实现每个字节大约需要1个周期。

  • 安全SipHash是强大的PRF(伪随机函数)。这意味着它与随机函数没有区别(除非您知道128位密钥)。因此:

    • 无需担心哈希表探针由于冲突而变成线性时间。使用SipHash,您知道您将获得平均情况下的平均性能,而与输入无关。

    • 不受基于散列的拒绝服务攻击的影响。

    • 您可以将SipHash(尤其是具有128位输出的版本)用作MAC(消息身份验证代码)。如果您收到一条消息和一个SipHash标签,并且该标签与使用您的秘密密钥运行SipHash的标签相同,则您知道创建哈希的任何人也都拥有您的秘密密钥,并且该消息和密码都没有。此后,哈希值已更改。

10
Demi

这取决于您要散列的数据。一些散列可以更好地处理特定数据,例如文本。一些哈希算法经过专门设计,可用于特定数据。

保罗·谢(Paul Hsieh)曾经做过 快速哈希 。他列出了源代码和说明。但是它已经被击败了。 :)

9
user712092

Java使用 this 简单的乘加算法:

字符串对象的哈希码计算为

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

使用int算术,其中s[i]是字符串的第i个字符,n是字符串的长度,而^表示幂。 (空字符串的哈希值为零。)

可能有更好的选择,但是这相当普遍,并且似乎在速度和唯一性之间进行了很好的权衡。

6
biziclop

首先,为什么需要实现自己的哈希?对于大多数任务,假设有可用的实现,您应该使用标准库中的数据结构获得良好的结果(除非您只是为了自己的学习而这样做)。

就实际的哈希算法而言,我个人最喜欢的是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;
}
4
user17754