什么是好的哈希函数?

Posted

技术标签:

【中文标题】什么是好的哈希函数?【英文标题】:What is a good Hash Function? 【发布时间】:2010-09-07 06:27:22 【问题描述】:

我在大学的数据结构课程中看到了很多散列函数和应用程序,但我大多都知道要制作一个好的散列函数非常困难。根据经验,为了避免碰撞,我的教授说:

function Hash(key)
  return key mod PrimeNumber
end

(mod 是 C 和类似语言中的 % 运算符)

用素数作为哈希表的大小。我知道这是避免碰撞和快速的一个很好的功能,但是我怎样才能做出更好的功能呢?字符串键对数字键有更好的哈希函数吗?

【问题讨论】:

您是否考虑过使用以下一种或多种通用哈希函数:partow.net/programming/hashfunctions/index.html 在fnv_func中,p[i]的类型是char,第一次迭代后h会发生什么?是故意的吗? @martinatime 说:在***en.wikipedia.org/wiki/Hash_function 中有大量关于哈希函数的信息,本文底部partow.net/programming/hashfunctions/index.html 有各种语言实现的算法。 【参考方案1】:

对于通用散列,没有“好的散列函数”之类的东西(编辑。是的,我知道有“通用散列”之类的东西,但这不是我的意思)。根据上下文不同的标准确定散列的质量。两个人已经提到了SHA。这是一个加密哈希,它对您可能的意思的哈希表一点也不好。

哈希表有非常不同的要求。但是,要普遍找到一个好的散列函数仍然很困难,因为不同的数据类型暴露了不同的可以散列的信息。作为一个经验法则,最好考虑一个类型拥有的所有信息。这并不总是容易的,甚至是不可能的。出于统计的原因(以及因此的碰撞),在问题空间(即所有可能的对象)上产生良好的分布也很重要。这意味着当散列 100 到 1050 之间的数字时,让最重要的数字在散列中发挥重要作用是不好的,因为对于大约 90% 的对象,这个数字将为 0。让最后三个数字更重要数字决定哈希。

同样,在对字符串进行散列处理时,考虑所有字符也很重要——除非事先知道所有字符串的前三个字符都相同;考虑这些就是浪费。

这实际上是我建议阅读 Knuth 在计算机编程的艺术中所说的话的一种情况,第一卷。 3. 另一个不错的读物是 Julienne Walker 的 The Art of Hashing。

【讨论】:

康拉德,从理论上讲你肯定是正确的,但你有没有试过使用我在评论中提到的 Paul Hsieh 哈希函数?它对很多不同类型的数据真的非常好! There's no such thing as a “good hash function” for universal hashes (ed. yes, I know there's such a thing as “universal hashing” but that's not what I meant). - “通用哈希”和“通用哈希”之间的含义有什么区别? @Abdul 没有。当我写这个答案时,我的措辞简直太糟糕了。我的意思是通用哈希函数只能保证预期的情况,即平均行为,而不是最坏情况的行为。但实际上,通用散列比我的回答听起来要好得多。 — 坦率地说,整个答案不是很好,今天我不会这样写开头的段落。【参考方案2】:

用于对基本上任何类型的数据进行“正常”的哈希表查找 - Paul Hsieh 的这个是我用过的最好的。

http://www.azillionmonkeys.com/qed/hash.html

如果您关心加密安全或其他更高级的东西,那么 YMMV。如果您只是想要一个用于哈希表查找的通用哈希函数,那么这就是您要寻找的。​​p>

【讨论】:

我从 Jenkins 的网站上读到 SFH 是当时最好的之一,但我认为 Murmur 可能会做得更好,请参阅这个出色的答案:programmers.stackexchange.com/questions/49550/… Hsieh 的哈希函数很糟糕,碰撞次数比我们想要的多一个数量级。特别是,仅在最后 4 个字节不同的字符串很容易发生冲突。如果您有一个 30 个字符的字符串,在最后 4 个字节中不同,则在处理 28 个字节后,哈希值仅在最后 2 个字节中不同。这意味着您可以保证剩余的两个字节值之一发生冲突。 (是的,它很快。那又怎样。)【参考方案3】:

散列函数有两个主要用途:

将数据点均匀地分散到 n 位中。 安全地识别输入数据。

在不知道你用它做什么的情况下推荐一个哈希是不可能的。

如果您只是在程序中创建哈希表,那么您无需担心算法的可逆性或可破解性... SHA-1 或 AES 完全不需要,您会最好使用variation of FNV。与您提到的简单素数模型相比,FNV 实现了更好的分散(因此更少的冲突),并且更适应不同的输入大小。

如果您使用散列来隐藏和验证公共信息(例如散列密码或文档),那么您应该使用经过公众审查的主要散列算法之一。 The Hash Function Lounge 是一个很好的起点。

【讨论】:

哈希函数休息室的更新链接:larc.usp.br/~pbarreto/hflounge.html 与 SHA1 的相同位数相比,FNV 承受生日碰撞的能力如何? @Kevin 只要散列的雪崩特性良好(输入的微小变化 = 输出的大变化),那么生日冲突只是散列中位的函数。 FNV-1a 在这方面非常出色,您可以根据需要在哈希中包含任意数量的位(尽管需要一些额外的努力才能获得不是 2 的幂的位数)。【参考方案4】:

这是一个很好的例子,也是一个你永远不想写的例子。 它是 Fowler / Noll / Vo (FNV) 哈希,相当于计算机科学天才和纯巫术:

unsigned fnv_hash_1a_32 ( void *key, int len ) 
    unsigned char *p = key;
    unsigned h = 0x811c9dc5;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x01000193;

   return h;


unsigned long long fnv_hash_1a_64 ( void *key, int len ) 
    unsigned char *p = key;
    unsigned long long h = 0xcbf29ce484222325ULL;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x100000001b3ULL;

   return h;

编辑:

Landon Curt Noll 在his site 上推荐 FVN-1A 算法优于原始 FVN-1 算法:改进后的算法可以更好地分散散列中的最后一个字节。我相应地调整了算法。

【讨论】:

您可能想查看此站点以获取有关选择这些值的原因的一些信息:isthe.com/chongo/tech/comp/fnv/#fnv-prime【参考方案5】:

我想说,主要的经验法则是不要自己动手。尝试使用经过彻底测试的东西,例如 SHA-1 或类似的东西。

【讨论】:

他似乎不需要任何加密安全的东西,所以 SHA-1 会有点矫枉过正。 顺便说一句,尽管没有发现 SHA-1 的冲突,但据信要数年或数月才能找到冲突。我建议使用 SHA-256。【参考方案6】:

一个好的散列函数具有以下属性:

    给定消息的哈希值,攻击者在计算上不可能找到另一条消息,使得它们的哈希值相同。

    给定一对消息 m' 和 m,要找到两个满足 h(m) = h(m') 的消息在计算上是不可行的

这两种情况相同。在第一种情况下,有一个预先存在的哈希,您正试图为其查找冲突。在第二种情况下,您试图找到 any 两条冲突的消息。由于生日“悖论”,第二个任务要容易得多。

在性能不是什么大问题的情况下,您应该始终使用安全的哈希函数。可以通过强制哈希中的冲突来执行非常聪明的攻击。如果你从一开始就使用强大的东西,你就会保护自己免受这些伤害。

不要在新设计中使用 MD5 或 SHA-1。大多数密码学家,包括我在内,都会认为它们被破坏了。这两种设计的主要弱点是我上面概述的第二个属性不适用于这些结构。如果攻击者可以生成两条消息,m 和 m',它们都哈希到相同的值,他们可以使用这些消息来对付你。 SHA-1 和 MD5 也会遭受消息扩展攻击,如果您不小心,可能会严重削弱您的应用程序。

像 Whirpool 这样更现代的哈希是更好的选择。它不受这些消息扩展攻击的影响,并使用与 AES 相同的数学方法来证明针对各种攻击的安全性。

希望有帮助!

【讨论】:

我认为在这种情况下推荐加密哈希函数是一个非常糟糕的建议。 @Slava:为什么?你有什么理由说“在这种情况下,加密哈希函数是一个非常糟糕的建议?”为什么这是不好的建议?造成这种情况的相对缺点是什么? @Mowzer 因为散列映射中使用的散列函数应该是快速和轻量级的(假设它仍然提供良好的散列),加密散列明确地在计算上很昂贵以防止暴力攻击。 【参考方案7】:

你在这里说的是你想要一个使用具有抗碰撞性的东西。尝试使用 SHA-2。或者尝试以单向压缩功能(以前从未尝试过)使用(好的)分组密码,例如 Miyaguchi-Preenel 模式中的 AES。问题在于您需要: 1)有一个IV。尝试使用 Khinchin 常数小数部分的前 256 位或类似的东西。 2)有一个填充方案。简单。从 MD5 或 SHA-3(Keccak [发音为 'ket-chak'])之类的哈希中取出它。 如果您不关心安全性(其他一些人这么说),请查看 Bob Jenkins 的 FNV 或 lookup2(实际上我是第一个推荐 lookup2 的人)也可以尝试 MurmurHash,它很快(检查这个:.16 cpb )。

【讨论】:

【参考方案8】:

一个好的散列函数应该

    双射,尽可能不丢失信息,并且冲突最少 尽可能多且均匀地级联,即每个输入位应该以 0.5 的概率翻转每个输出位,并且没有明显的模式。 如果在加密上下文中使用,则不应该存在有效的反转它的方法。

素数模不满足任何这些点。这根本不够。它通常总比没有好,但它甚至不快。乘以一个无符号整数并取一个 2 的幂的模数也能很好地分布值,这根本不是很好,但只有大约 2 个 cpu 周期,它比素数模数需要的 15 到 40 快得多(是的,整数除法真的很慢)。

要创建一个快速并能很好地分配值的哈希函数,最好的选择是用质量较差的快速排列组合它,就像他们为随机数生成所做的 PCG 一样。

有用的排列包括:

与一个不均匀的整数相乘 二进制旋转 xorshift

按照这个配方,我们可以创建自己的hash function,或者我们采用经过测试并被广泛接受的splitmix。

如果需要加密特性,我强烈建议使用 sha 系列的函数,该函数经过了良好的测试和标准化,但出于教育目的,您可以这样做:

首先你采用一个好的非加密散列函数,然后你应用一个单向函数,比如在素数域上取幂或k(n*(n+1)/2) mod 2^k 的许多应用,当k 是生成的哈希中的位。

【讨论】:

【参考方案9】:

我强烈推荐 SMhasher GitHub 项目 https://github.com/rurban/smhasher,它是哈希函数的测试套件。这里列出了没有已知质量问题的最快的最先进的非加密哈希函数:https://github.com/rurban/smhasher#summary。

【讨论】:

以上是关于什么是好的哈希函数?的主要内容,如果未能解决你的问题,请参考以下文章

什么是 Java 中用于文本字符串的好的 64 位哈希函数?

哈希(hash) - 哈希算法的应用

什么是英语单词的好散列函数?

哈希是好东西,二分+哈希是更好的东西

128 哈希函数

lintcode 128哈希函数