【问题标题】:Python2 hash values badly distributedPython2 哈希值分布不均
【发布时间】:2017-11-24 20:48:30
【问题描述】:

当我在字符串上使用 Python 内置的 hash() 函数时,我只是在玩它时发现了一些奇怪的东西。通常,一个正常的哈希函数应该是不相关的,从hash(A)hash(B) 应该是完全不可识别的(对于不相关/不可识别的充分定义)。

但是,这个快速的小脚本显示不同

In [1]: for i in range(15):
...:     print hash('test{0}'.format(i))
...:
-5092793511388848639
-5092793511388848640
-5092793511388848637
-5092793511388848638
-5092793511388848635
-5092793511388848636
-5092793511388848633
-5092793511388848634
-5092793511388848631
-5092793511388848632
5207588497627702649
5207588497627702648
5207588497627702651
5207588497627702650
5207588497627702653

我了解 Python 的 hash() 函数在任何情况下都不应该是加密安全的,为此您将使用 hashlib 库,但为什么 testX 的值如此定期分布?在我看来,它的碰撞行为可能很差。

【问题讨论】:

  • Python 3 似乎已经解决了这个问题。使用 Python 3,传播范围更广。
  • 哈希函数用于快速进行字典/集合索引,而不是像 MD5 那样防止冲突(保护更好但计算成本更高)
  • @jonrsharpe 啊,防止 DoS 的自动盐。好吧,它仍然解决了字符串过于相似的问题。
  • 对我来说看起来像 完美 碰撞行为。你试过for i in range(15): print(hash(i))吗?

标签: python python-2.7 hash language-design


【解决方案1】:

哈希是一个接一个地计算的。这就是哈希值如此相似的原因。

在计算期间,"test0""test1" 具有完全相同的哈希值,直到 "test"。在最后一个字符中只有一点区别。在安全散列中,在任何地方更改一位应该会完全更改整个散列(例如,由于多次传递)。

您可以通过计算“0test”和“1test”的哈希来检查这种行为:

>>> for i in range(15):
...     print hash('{0}test'.format(i))
... 
-2218321119694330423
-198347807511608008
-8430555520134600289
1589425791872121742
-6642709920510870371
-4622800608552147860
8038463826323963107
2058173137418684322
-8620450647505857711
-6600477335291135136
8795071937164440413
4111679291630235372
-765820399655801141
2550858955145994266
6363120682850473265

这是您所期望的广泛分布,对吧?顺便说一句,Python 3 似乎对字符串有不同的哈希计算。

有关 Python2 字符串哈希的更多信息,请查看"Python Hash Algorithms"

class string:
    def __hash__(self):
        if not self:
            return 0 # empty
        value = ord(self[0]) << 7
        for char in self:
            value = c_mul(1000003, value) ^ ord(char)
        value = value ^ len(self)
        if value == -1:
            value = -2
        return value

顺便说一下,这个问题与 Python 无关。在 Java 中,"Aa""BB" 共享相同的哈希值。

【讨论】:

    【解决方案2】:

    pythonhash 函数不是加密哈希(即不能防止碰撞或显示雪崩效应等);它只是对象的标识符(例如用作字典键)。

    在文档中阅读有关 __hash__hash 的更多信息。

    如前所述:

    dict. __hash__() 应该返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值

    并且 - 正如Jean-François Fabre 在评论中指出的那样 - python 散列必须很快(即构建字典)。加密哈希很慢,因此无法使用。

    顺便说一句:在 python 3 中,分布看起来更加随机。

    【讨论】:

    • 我确定它不是唯一的。例如:&gt;&gt;&gt; a = 449304920394029304920394029304203940293042 &gt;&gt;&gt; b = 449304920394029304920394029304203940293042 &gt;&gt;&gt; a is b False &gt;&gt;&gt; hash(a)==hash(b) True。这取决于对象的内容
    • @Jean-FrançoisFabre :把它拿出来。对。你说的对!无论如何你都可以覆盖......
    • 至少现在你的答案没有错。我会补充(只是一个猜测)哈希需要足够快,而不像像 MD5 这样昂贵的“强”哈希。正如您所暗示的,目的不是避免冲突,而是要足够快地散列密钥。
    • 确实,它不可能是唯一的。比 64 位整数有更多可能的对象和字符串。
    【解决方案3】:

    解释可以在python2.7的Objects/dictobject.c的源码的cmets中找到:

    前面的主要细节:大多数哈希方案都依赖于“好” 散列函数,在模拟随机性的意义上。 Python 不会: 它最重要的哈希函数(用于字符串和整数)非常 普通情况下的常规:

    >>> map(hash, (0, 1, 2, 3)) 
    [0, 1, 2, 3]
    >>> map(hash, ("namea", "nameb", "namec", "named"))
    [-1658398457, -1658398460, -1658398459, -1658398462]
    

    这不一定是坏事!相反,在大小为 2**i 的表中, 将低位 i 位作为初始表索引是极 快速,并且对于由 a 索引的 dicts 根本没有冲突 连续的整数范围。当键是 “连续”的字符串。所以这给出了比随机更好的行为 常见的情况,这是非常可取的。

    【讨论】:

      猜你喜欢
      • 2016-02-07
      • 1970-01-01
      • 2015-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多