为什么XOR是组合哈希的默认方法?


145

假设您有两个哈希H(A)H(B)并且想要将它们组合在一起。我读过,将两个散列组合在一起的一种好方法是XOR,例如XOR( H(A), H(B) )

这些哈希函数准则在此简要地介绍了我找到的最佳解释:

对具有大致随机分布的两个数字进行异或运算会导致另一个仍具有大致随机分布*的数字,但是现在取决于这两个值。
...
*在要组合的两个数字的每一位,如果两位相等,则输出0,否则为1。换句话说,在50%的组合中,将输出1。因此,如果两个输入位各自有大约50-50的可能性为0或1,那么输出位也是如此。

您能解释为什么XOR应该是用于组合哈希函数(而不是OR或AND等)的默认操作的直觉和/或数学方法吗?


20
我想你只是做过;)
马萨

22
请注意,根据您在“组合”中的要求,异或可能不是“组合”哈希的“好方法”。XOR是可交换的:XOR(H(A),H(B))等于XOR(H(B),H(A))。这意味着XOR不是一种创建值的有序序列的哈希的正确方法,因为它不捕获顺序。
Thomas Pornin 2011年

6
除了顺序问题(上面的注释)之外,还存在值相等的问题。XOR(H(1),H(1))= 0(对于任何函数H),XOR(H(2),H(2))= 0等。对于任何N:XOR(H(N),H(N))= 0。相等的值在真实的应用程序中经常发生,这意味着XOR的结果经常为0,以至于不能视为良好的哈希。
Andrei Galatyn '16

您对有序的值序列使用什么?假设我想创建一个时间戳或索引的哈希。(MSB不如LSB重要)。抱歉,如果该线程是1岁。
Alexis

Answers:


120

假设均匀随机(1位)输入,AND函数输出概率分布分别为75%0和25%1。相反,OR为25%0和75%1

XOR函数为50%0和50%1,因此对于组合均匀的概率分布非常有用。

通过写出真值表可以看出:

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

 a | b | a OR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    1

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

练习:两个1位输入具有多少逻辑功能,a并且b具有均匀的输出分布?为什么XOR最适合您提出问题的目的?


24
应答给锻炼:从16点可能的不同一个XXX B这样的操作(0, a & b, a > b, a, a < b, b, a % b, a | b, !a & !b, a == b, !b, a >= b, !a, a <= b, !a | !b, 1),下面具有0和1的50%-50%的分布,假设a和b具有0和1的50%-50%的分布:a, b, !a, !b, a % b, a == b即相对XOR(EQUIV)也可以使用...
马萨

7
格雷格,这是一个了不起的答案。当我看到您的原始答案并写出自己的真值表后,灯泡为我点亮了。我考虑了@Massa的答案,即如何进行6种合适的操作来维护发行版。尽管a, b, !a, !b它们的输入与它们的输入具有相同的分布,但是您失去了其他输入的熵。也就是说,XOR最适合用于组合哈希的目的,因为我们要从a和b中捕获熵。
内特·默里

1
这是一篇论文,说明在不输出少于每个哈希值位数之和的位数的情况下,安全地组合哈希的情况是不可能的,其中每个函数只能调用一次。这表明此答案不正确。
陶Szelei

3
@Massa我从未见过%用于XOR或不相等。
Buge 2014年

7
正如Yakk指出的那样,异或会很危险,因为对于相同的值它会产生零。这个装置(a,a)(b,b)两个产生零,这在许多(大多数?)的情况下显着提高基于散列的数据结构冲突的可能性。
德鲁·诺阿克斯

170

xor是在散列时使用的危险的默认函数。它比and和更好or,但这并不多。

xor是对称的,因此元素的顺序丢失了。因此,"bad"哈希组合与相同"dab"

xor 将成对的相同值映射为零,并且应避免将“公共”值映射为零:

因此,(a,a)被映射为0,(b,b)也被映射为0。由于此类对几乎总是比随机性所暗示的更为普遍,因此最终在零处产生的碰撞要多得多。

遇到这两个问题,xor最终成为一个哈希混合器,看起来表面上还不错,但经过​​进一步检查后却没有。

在现代硬件上,添加速度通常与添加速度差不多xor(公认的,它可能会使用更多功能来实现此目的)。加法运算的真值表与所xor讨论的位类似,但是当两个值均为1时,它还会向下一位发送一个位。这意味着它将删除较少的信息。

因此hash(a) + hash(b)要比hash(a) xor hash(b)if 更好a==bhash(a)<<1而不是0。

这保持对称;所以"bad""dab"得到同样的结果仍然是一个问题。我们可以以适度的成本打破这种对称性:

hash(a)<<1 + hash(a) + hash(b)

又名hash(a)*3 + hash(b)。(hash(a)如果使用班次解决方案,则建议一次计算并存储)。而不是任何奇数常量,3将双射地将一个“ k-bit”无符号整数映射到其自身,因为无符号整数的映射对2^k某些对象而言是数学模k,并且任何奇数常量都相对于2^k

对于更高级的版本,我们可以检查boost::hash_combine,这实际上是:

size_t hash_combine( size_t lhs, size_t rhs ) {
  lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
  return lhs;
}

在这里,我们将一些seed带有常数的移位版本加在一起(基本上是随机的0s和1s,尤其是32位固定点分数的黄金分割率的倒数),加上一些加法和一个xor。这打破对称,并介绍了一些“噪声”,如果传入的散列值是差(即,每一个部件散列想象到0 -上述处理得很好,产生的涂抹10。s各自结合后我的幼稚3*hash(a)+hash(b)简单地输出一个0在这种情况)。

(对于不熟悉C / C ++的人,a size_t是一个无符号整数值,该值足以描述内存中任何对象的大小。在64位系统上,它通常是64位无符号整数。在32位系统上,一个32位无符号整数。)


很好的回答Yakk。此算法在32位和64位系统上是否都同样有效?谢谢。
戴夫

1
@dave将更多位添加到0x9e3779b9
Yakk-亚当·内夫罗蒙特2015年

10
好的,要完整...这是全精度64位常量(使用长双精度和无符号长整数计算):0x9e3779b97f4a7c16。有趣的是,它仍然是偶数。使用PI而不是黄金比率重新进行相同的计算会产生:0x517cc1b727220a95,它是奇数而不是偶数,因此可能比另一个常数“质数更多”。我用过:std :: cout << std :: hex <<(unsigned long long)(((1.0L / 3.14159265358979323846264338327950288419716939937510L)*(powl(2.0L,64.0L)))<< std :: endl; 使用cout.precision(numeric_limits <long double> :: max_digits10); 再次感谢Yakk。
戴夫2015年

2
在这些情况下,@ Dave逆黄金比例规则是等于或大于您正在执行的计算的第一个奇数。因此,只需添加1。这是一个重要的数字,因为N *比率的序列,最大模数(此处为2 ^ 64),将序列中的下一个值恰好以该比率位于最大“间隙”的中间数字。在网络上搜索“斐波那契哈希”以获取更多信息。
斯科特·凯里

1
@正确的数字将是0.9E3779B97F4A7C15F39 ...请参阅链接。您可能会遇到四舍五入规则(这对会计师很有用),或者简单地,如果您从文字sqrt(5)常数开始,则当减去1时,您将删除高阶位,即一定是丢失了。
migle

29

尽管XOR具有方便的位混合特性,但由于具有可交换性,因此也不是组合哈希的好方法。考虑一下如果将{1、2,…,10}的置换存储在10元组的哈希表中会发生什么。

更好的选择是m * H(A) + H(B),其中m是一个大的奇数。

信用:以上组合器是鲍勃·詹金斯的秘诀。


2
有时可交换性是一件好事,但XOR是一个糟糕的选择,即使是这样,因为配套项目的所有对将获得散列到零。算术和更好。一对匹配项的哈希将仅保留31位有用数据,而不是32位,但这比保留零更好。另一个选择可能是将算术和作为a进行计算long,然后再将上部和下部相塞。
supercat

1
m = 3实际上,它是一个不错的选择,并且在许多系统上都非常快。请注意,对于任何奇数m整数乘法都是模2^322^64,因此是可逆的,因此您不会丢失任何位。
StefanKarpinski 2014年

当您超出MaxInt会发生什么?
2014年

2
而不是任何奇数,应该选择一个质数
TermoTux 2014年

2
@Infinum在组合哈希时不需要。
马塞洛·坎托斯

17

Xor可能是组合哈希的“默认”方式,但是Greg Hewgill的答案也表明了它有陷阱的原因:两个相同哈希值的Xor为零。在现实生活中,存在相同的散列比人们预期的更常见。然后,您可能会发现,在这些(不是那么少见的)极端情况下,所得的组合哈希值始终相同(零)。哈希冲突比您预期的要频繁得多。

在一个人为的示例中,您可能正在组合来自您管理的不同网站的用户的哈希密码。不幸的是,大量用户重复使用了他们的密码,并且产生的哈希值中令人惊讶的比例为零!


我希望这个伪造的例子永远不会发生,密码应该加盐。
user60561

8

我想为找到此页面的其他人明确指出一些内容。AND和OR限制输出,例如BlueRaja-Danny Pflughoe试图指出,但可以更好地定义:

首先,我想定义两个简单的函数来解释这一点:Min()和Max()。

Min(A,B)将返回A和B之间较小的值,例如:Min(1,5)返回1。

Max(A,B)将返回在A和B之间较大的值,例如:Max(1,5)返回5。

如果给出: C = A AND B

然后,您会发现C <= Min(A, B)我们知道这一点,因为您无法将A或B的0位与AND设为1。因此,每个零位保持为零位,并且每个位都有机会变为零位(因此值较小)。

带有: C = A OR B

相反的情况是:C >= Max(A, B)这样,我们看到了AND函数的推论。任何已经为1的位都不能被或为0,因此它保持为1,但是每个零位都有机会变为1,从而变成更大的数字。

这意味着输入的状态对输出施加了限制。如果与90进行任何运算,您将知道输出等于或小于90,而不管其他值是多少。

对于XOR,没有基于输入的隐含限制。在某些特殊情况下,您会发现,如果将一个字节与255进行XOR运算,则会得到相反的值,但是可以从中输出任何可能的字节。每一位都有机会根据其他操作数中的同一位更改状态。


6
可以说那OR按位最大值,而又AND按位最小值
圣保罗Ebermann

Paulo Ebermann说得很好。很高兴在这里见到您以及Crypto.SE!
Corey Ogburn

我创建了一个过滤器,其中包括标记了密码学的所有内容,还更改了旧问题。这样,我在这里找到了答案。
2011年

3

如果您XOR是带有偏置输入的随机输入,则输出是随机的。AND或并非如此OR。例:

00101001 XOR 00000000 = 00101001
00101001和00000000 = 00000000
00101001或11111111 = 11111111

正如@Greg Hewgill提到的,即使两个输入都是随机的,使用ANDOR也会导致输出有偏差。

我们之所以使用XOR更复杂的东西,是因为,没有必要: XOR完美地工作,而且速度极快。


1

覆盖左侧的两列,并尝试仅使用输出确定输入的含义。

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

当您看到一个1位时,您应该已经确定两个输入均为1。

现在对XOR执行相同的操作

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

XOR不放弃任何输入。


0

对于各种版本的源代码hashCode()java.util.Arrays中为固体,一般使用的散列算法有很大的参考。它们很容易理解,并被翻译成其他编程语言。

粗略地说,大多数多属性hashCode()实现遵循以下模式:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

您可以搜索其他StackOverflow问答,以获取有关背后的魔力31以及Java代码如此频繁使用它的更多信息。它虽然不完美,但是具有非常好的总体性能特征。


2
Java的默认“乘以31并加/累加”哈希值加载了冲突(例如,任何stringstring + "AA"IIRC 发生冲突的冲突),并且很久以前他们希望自己没有将该算法纳入规范。也就是说,使用更大的奇数并设置更多的位,并增加移位或旋转数可以解决该问题。MurmurHash3的“ mix”可以做到这一点。
Scott Carey

0

XOR不会忽略某些输入,例如ORAND

如果以AND(X,Y)为例,并为输入X提供false,则输入Y无关紧要……并且在组合哈希时可能希望输入具有重要性。

如果您采用XOR(X,Y),两个输入始终很重要。X与Y无关紧要,将没有X的值。如果更改X或Y,则输出将反映出来。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.