Python 3.3 中的哈希函数在会话之间返回不同的结果

Posted

技术标签:

【中文标题】Python 3.3 中的哈希函数在会话之间返回不同的结果【英文标题】:hash function in Python 3.3 returns different results between sessions 【发布时间】:2015-02-15 19:50:00 【问题描述】:

我在 python 3.3 中实现了一个 BloomFilter,每次会话都得到不同的结果。深入研究这种奇怪的行为,我找到了内部 hash() 函数——它每次会话都会为同一个字符串返回不同的哈希值。

例子:

>>> hash("235")
-310569535015251310

-----打开一个新的python控制台-----

>>> hash("235")
-1900164331622581997

为什么会这样? 为什么这有用?

【问题讨论】:

【参考方案1】:

Python 使用随机散列种子来防止攻击者通过向您发送旨在碰撞的密钥来对您的应用程序进行 tar 攻击。请参阅original vulnerability disclosure。通过使用随机种子(在启动时设置一次)抵消散列,攻击者无法再预测哪些密钥会发生冲突。

您可以通过设置PYTHONHASHSEED environment variable 来设置固定种子或禁用该功能;默认为random,但您可以将其设置为固定的正整数值,0 完全禁用该功能。

Python 2.7 和 3.2 版本默认禁用该功能(使用-R 开关或设置PYTHONHASHSEED=random 启用它);它在 Python 3.3 及更高版本中默认启用。

如果您依赖 Python 集中键的顺序,请不要这样做。 Python 使用哈希表来实现这些类型及其顺序depends on the insertion and deletion history 以及随机哈希种子。请注意,在 Python 3.5 及更早版本中,这也适用于字典。

另见object.__hash__() special method documentation:

注意:默认情况下,str、bytes 和 datetime 对象的 __hash__() 值是用不可预测的随机值“加盐”的。尽管它们在单个 Python 进程中保持不变,但它们在 Python 的重复调用之间是不可预测的。

这是为了防止由精心选择的输入引起的拒绝服务,这些输入利用字典插入的最坏情况性能,O(n^2) 复杂度。详情请见http://www.ocert.org/advisories/ocert-2011-003.html

更改哈希值会影响 dicts、sets 和其他映射的迭代顺序。 Python 从未对这种顺序做出保证(它通常在 32 位和 64 位版本之间变化)。

另见PYTHONHASHSEED

如果你需要一个稳定的哈希实现,你可能想看看hashlib module;这实现了加密哈希函数。 pybloom project uses this approach。

由于偏移量由前缀和后缀(分别为起始值和最终异或值)组成,因此遗憾的是,您不能只存储偏移量。从好的方面来说,这确实意味着攻击者也无法通过定时攻击轻松确定偏移量。

【讨论】:

我希望这会出现在 hash() 文档中,而不仅仅是 __hash__() 中。 +1 一个很好的答案。 p.s. hashlib 对哈希函数的非加密用途来说是不是有点过头了? pybloom 使用 hashlib 函数。但是如果你想要更快的东西,你可以查看pyhash。 为什么设置为0的时候文档叫disable?除非我遗漏了什么,否则我看不出将其设置为任何旧的稳定种子编号的有效区别。我的意思是,当我使用PYTHONHASHSEED=12345 时,即使跨会话,我也会为相等的字符串获得相同的散列 - 当我使用 PYTHONHASHSEED=0 时也会发生同样的情况 - 跨会话的相等字符串的散列将是相同的(尽管与 12345 不同,但很明显,这就是种子的工作原理)。 @MartijnPieters 受影响的哈希“根本没有种子”是什么意思?拥有例如 12345 的种子的语义或质量差异是什么,除了它创建了两组不同的会话集,其中哈希值不同并且 PYTHONHASHSEED=0 等于旧版本之外?你能把我链接到一段特定的源代码吗?我想我的意思是,如果没有这样的区别,我会称它为 0 的种子,而旧版本的 Python 仅支持 0 的种子。目前的文档对我来说非常混乱。 @jtlz2:通过占用所有 CPU 时间来拒绝服务。【参考方案2】:

哈希随机化是turned on by default in Python 3。这是一项安全功能:

哈希随机化旨在防止由精心选择的输入引起的拒绝服务,这些输入利用 dict 构造的最坏情况性能

在 2.6.8 之前的版本中,您可以在命令行中使用 -R 或 PYTHONHASHSEED 环境选项将其打开。

您可以通过将PYTHONHASHSEED 设置为零来关闭它。

【讨论】:

【参考方案3】:

hash() 的这种行为让我在尝试比较会话之间保存在数据库中的记录时感到困惑。

PYTHONHASHSEED 解决方案过于复杂,因为我需要我的程序能够可靠地工作,独立于环境变量设置。

所以我创建了一个简单的 has 函数,它对字符串进行哈希处理(很容易将任何内容转换为字符串)并生成一个 32 位正整数作为哈希值。它不是加密安全的哈希,但足以进行快速比较。

def myHash(text:str):
  hash=0
  for ch in text:
    hash = ( hash*281  ^ ord(ch)*997) & 0xFFFFFFFF
  return hash

乘法中的数字只是为了混淆位而任意选择的素数。

如果您希望哈希为十六进制字符串,可以将最后一行替换为:

return hex(hash)[2:].upper().zfill(8)

【讨论】:

以上是关于Python 3.3 中的哈希函数在会话之间返回不同的结果的主要内容,如果未能解决你的问题,请参考以下文章

symfony 3.3中的单个会话实现

Java集合哈希表及哈希函数的实现方式

Python中的加密哈希函数

3.3 TensorFlow运行模型 ------- 会话

数据结构与算法——哈希函数和哈希表等

。net中哈希函数基础问题