什么是好的哈希函数?在我的大学数据结构课程中,我看到了许多哈希函数和应用程序,但基本上我得出的结论是很难编写一个好的哈希函数。作为一个避免冲突的经验法则,我的教授说:
function Hash(key)
return key mod PrimeNumber
end
(mod是C语言和类似语言中的%运算符)
使用质数作为哈希表的大小。 我知道这是一种避免冲突和快速的好方法,但我怎样才能做得更好呢? 是否有更好的字符串键和数字键的哈希函数?
什么是好的哈希函数?在我的大学数据结构课程中,我看到了许多哈希函数和应用程序,但基本上我得出的结论是很难编写一个好的哈希函数。作为一个避免冲突的经验法则,我的教授说:
function Hash(key)
return key mod PrimeNumber
end
(mod是C语言和类似语言中的%运算符)
使用质数作为哈希表的大小。 我知道这是一种避免冲突和快速的好方法,但我怎样才能做得更好呢? 是否有更好的字符串键和数字键的哈希函数?
对于通用哈希函数而言,并不存在所谓的“好的哈希函数”(注:我知道有“通用哈希”的概念,但那不是我想说的)。因为在不同的情况下,不同的标准决定了哈希的质量。两个人已经提到了SHA。它是一种加密哈希,但对于你可能想要的哈希表来说并不好。
哈希表具有非常不同的要求。但是,由于不同的数据类型公开了可以哈希的不同信息,因此普遍找到一个好的哈希函数很困难。作为经验法则,最好平等地考虑一个类型持有的所有信息。这并不总是容易甚至可能不可能。出于统计学原因(因此发生冲突),生成问题空间(即所有可能的对象)上的良好散布也很重要。这意味着当哈希数字介于100和1050之间时,让最高位数字在哈希中占主导地位并不好,因为对于约90%的对象,该数字将为0。让最后三个数字确定哈希值更加重要。
同样,在哈希字符串时,重要的是考虑所有字符 - 除非预先知道所有字符串的前三个字符都相同;在这种情况下,考虑这些字符是浪费的。
实际上,这是我建议阅读Knuth在《计算机程序设计艺术》第3卷中所说的内容之一。另一个好的读物是Julienne Walker的哈希的艺术。
对于基本上任何类型的数据,如果需要进行“普通”哈希表查找,Paul Hsieh 的这个哈希函数是我用过的最好的一个。
http://www.azillionmonkeys.com/qed/hash.html
如果您关心密码安全或其他更高级的内容,则可能因人而异。 如果您只想要一个绝佳的通用哈希函数用于哈希表查找,那么这就是您要寻找的东西。
这是一个好的例子,也是为什么你永远不想写一个的例子。它是一个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;
}
编辑:
我认为最重要的原则是不要自己开发。尽量使用经过彻底测试的东西,例如SHA-1或类似的东西。
一个好的哈希函数具有以下特性:
给定一条消息的哈希值,对于攻击者来说,计算找到另一条消息使它们的哈希值相同是计算上不可行的。
给定一对消息m'和m,计算找到两个消息使得h(m)=h(m')是计算上不可行的。
这两种情况不是一样的。在第一种情况下,存在一个预先存在的哈希值,你正在尝试找到一个碰撞。而在第二种情况下,你正在尝试找到任意两条碰撞的消息。由于生日“悖论”,第二个任务要简单得多。
当性能不是重要问题时,你应该始终使用安全的哈希函数。攻击者可以通过强制哈希碰撞来进行非常巧妙的攻击。如果你一开始就使用了强大的哈希函数,你将保护自己免受这些攻击。
在新设计中不要使用MD5或SHA-1。大多数密码学家,包括我在内,都认为它们已经被破解了。这两种设计的主要弱点是第二个属性,即我上面概述的属性,在这些构造中不成立。如果攻击者可以生成两个消息m和m',它们都散列到相同的值,他们可以利用这些消息来攻击你。如果您不小心,SHA-1和MD5也会受到消息扩展攻击,这可能会严重削弱您的应用程序。
像Whirpool这样更现代的哈希算法是更好的选择。它不会受到这些消息扩展攻击的影响,并使用与AES相同的数学方法来证明针对各种攻击的安全性。
希望这有所帮助!
质数模数不满足这些要点。它只是不够好。它往往比什么都不做要好,但它甚至并不快速。使用无符号整数进行乘法并取2的幂模可以同样好地分配值,但是仅需要大约2个CPU周期,而质数模数需要15至40个周期(是的,整数除法确实如此缓慢)。
为了创建一个既快速又分布良好的哈希函数,最好的选择是从具有较低质量的快速排列组合而成,就像他们为随机数生成所做的PCG一样。
有用的排列包括:
按照这个方法,我们可以创建自己的哈希函数或使用经过测试和广泛接受的splitmix。
如果需要加密质量,我强烈建议使用sha系列函数,这些函数经过了充分的测试和标准化,但出于教育目的,以下是如何创建一个:
首先,您需要选择一个良好的非加密哈希函数,然后应用一个单向函数,如素域上的指数运算或k
次应用(n*(n+1)/2) mod 2^k
,其中k
是结果哈希中的位数,并在其中插入一个xorshift。