ChatGPT解决这个技术问题 Extra ChatGPT

什么是好的哈希函数?

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

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有各种语言实现的算法。

K
Konrad Rudolph

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

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

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

这实际上是我建议阅读 Knuth 在计算机编程的艺术中所说的情况之一,第一卷。 3. 另一本好书是 Julienne Walker 的The Art of Hashing


Konrad,从理论的角度来看,你肯定是正确的,但你有没有尝试过使用我在评论中提到的 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 没有。当我写下这个答案时,我的措辞简直太糟糕了。我的意思是通用散列函数只能保证预期的情况,即平均行为,而不是最坏情况的行为。但实际上,通用散列比我的回答听起来要好得多。 — 坦率地说,整个答案不是很好,今天我不会这样写开头的段落。
C
Chris Harris

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

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

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


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

散列函数有两个主要目的:

将数据点均匀地分散到 n 位中。

以安全地识别输入数据。

在不知道您使用它的目的的情况下推荐哈希是不可能的。

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

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


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

这是一个很好的例子,也是一个你永远不想写的例子。它是 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 在他的网站上推荐 FVN-1A 算法优于原始 FVN-1 算法:改进后的算法更好地分散散列中的最后一个字节。我相应地调整了算法。


您可能想查看此网站以了解有关选择这些值的原因的一些信息:isthe.com/chongo/tech/comp/fnv/#fnv-prime
E
Einar

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


他似乎不需要任何加密安全的东西,所以 SHA-1 会有点矫枉过正。
顺便说一句,尽管没有发现 SHA-1 的碰撞,但据信在发现碰撞之前需要几年或几个月的时间。我建议使用 SHA-256。
S
Simon Johnson

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

给定消息的哈希值,攻击者在计算上不可能找到另一条消息,使得它们的哈希值相同。给定一对消息 m' 和 m,要找到两个满足 h(m) = h(m') 的消息在计算上是不可行的

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

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

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

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

希望有帮助!


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

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


W
Wolfgang Brehm

一个好的散列函数应该

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

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

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

有用的排列包括:

与不均匀整数相乘

二进制旋转

异或移位

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

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

首先,您采用一个良好的非加密散列函数,然后您应用单向函数,例如在素数字段上取幂或 kk 是结果哈希。


o
otmar

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