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

时间:2011-05-04 20:07:14

标签: cryptography bit-manipulation hash probability xor

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

我在这些hash function guidelines上简要介绍了我发现的最佳解释:

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

你能解释为什么XOR应该是组合散列函数(而不是OR或AND等)的默认操作的直觉和/或数学吗?

9 个答案:

答案 0 :(得分:145)

xor是散列时使用的危险默认函数。它比和和更好,但是并没有多说。

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

xor将相同的值映射到零,你应该避免映射" common"值为零:

所以(a,a)被映射到0,而(b,b)也被映射到0.因为这样的对比随机性更常见,你最终会得到远远多于零的碰撞

有了这两个问题,xor最终成为一个散列组合器,表面看起来不太合适,但在进一步检查后却没有。

在现代硬件上,通常以xor的速度添加(它可能会使用更多的功率来实现这一点)。添加的真值表与所讨论的位上的xor类似,但当两个值均为1时,它也会向下一位发送一个位。这会消除较少的信息。

所以hash(a) + hash(b)更好,如果a==b,则结果是hash(a)<<1而不是0。

这仍然是对称的。我们可以以适度的成本打破这种对称性:

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

又名hash(a)*3 + hash(b)。 (计算hash(a)一次,如果您使用班次解决方案,建议存储)。任何奇数常数而不是3都会将size_t(或k位无符号常量)双射地映射到自身,因为对于某些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的一些移位版本与常量(基本上是随机0 s和1 s - 加在一起,特别是它是黄金比例的倒数具有一些加法和xor的32位定点分数。这打破了对称性,并引入了一些&#34;噪音&#34;如果传入的散列值很差(即,想象每个分量哈希值为0 - 上面的处理很好,在每个组合后产生10 s的拖尾。我只是输出一个{{ 1}})。

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

答案 1 :(得分:107)

假设均匀随机(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位输入ab有多少逻辑函数具有均匀的输出分布?为什么XOR最适合您问题中所述的目的?

答案 2 :(得分:29)

尽管它具有方便的位混合特性,但由于其可交换性,XOR 不是组合哈希的好方法。考虑如果将{1,2,...,10}的排列存储在10元组的哈希表中会发生什么。

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

信用:上述合并器是Bob Jenkins的提示。

答案 3 :(得分:16)

Xor可能是结合哈希的“默认”方式,但Greg Hewgill的回答也说明了它有其陷阱的原因: 两个相同散列值的xor为零。 在现实生活中,有相同的哈希比人们预期的更为常见。然后,您可能会发现在这些(并非如此罕见)的极端情况下,生成的组合哈希值始终相同(零)。哈希碰撞会比你预期的要频繁得多。

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

答案 4 :(得分:8)

答案 5 :(得分:2)

如果XOR带有偏置输入的随机输入,则输出是随机的。 ANDOR也是如此。例如:

00101001 XOR 00000000 = 00101001
00101001 AND 00000000 = 00000000
00101001 OR  11111111 = 11111111

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

我们将XOR用于更复杂的事情的原因是,嗯,没有必要:XOR完美无缺,而且它非常愚蠢。

答案 6 :(得分:0)

java.util.ArrayshashCode()的各种版本的源代码是可靠,通用的散列算法的绝佳参考。它们易于理解并翻译成其他编程语言。

粗略地说,大多数多属性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 Q&amp; As,了解有关31背后的魔力的更多信息,以及Java代码经常使用它的原因。它不完美,但具有很好的一般性能特征。

答案 7 :(得分:0)

覆盖左侧2列并尝试使用输出计算出输入的内容。

 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没有提供任何关于它的输入。

答案 8 :(得分:0)

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

如果您以 AND(X,Y)为例,并为输入 X 输入false,则输入 Y 无关紧要...而且在组合哈希时,可能希望输入很重要。

如果您采用 XOR(X,Y),则两者输入 ALWAYS 很重要。如果Y无关紧要,那么将没有X的值。如果更改了X或Y,则输出将反映出来。