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

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

例如:

>>> hash("235")
-310569535015251310

- 打开一个新的巨蟒控制台

>>> hash("235")
-1900164331622581997

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

62613 次浏览

Python 使用一个随机散列种子来防止攻击者通过向您发送旨在发生碰撞的密钥来破坏应用程序。看看 原始漏洞披露。通过使用随机种子(在启动时设置一次)偏移散列,攻击者不再能够预测哪些键将发生碰撞。

您可以通过设置 环境变量来设置一个固定的种子或禁用该特性; 默认值是 random,但是您可以将其设置为一个固定的正整数值,而 0则完全禁用该特性。

Python 版本2.7和3.2默认禁用了这个特性(使用 -R开关或设置 PYTHONHASHSEED=random来启用它) ; 在 Python 3.3和更高版本中默认启用了这个特性。

如果您依赖于 Python 集中键的顺序,那么不要这样做。Python 使用哈希表来实现这些类型及其顺序 取决于插入和删除历史以及随机哈希种子。注意,在 Python 3.5及以上版本中,这也适用于字典。

请参阅 object.__hash__()特殊方法文件:

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

这是为了提供保护,以防止由于精心选择的输入导致的拒绝服务,这些输入利用了 dict 插入的最坏情况性能,即 O (n ^ 2)复杂性。详情请参阅 http://www.ocert.org/advisories/ocert-2011-003.html

改变哈希值会影响字节、集合和其他映射的迭代顺序。Python 从未保证过这种顺序(它通常在32位和64位构建之间变化)。

参见 PYTHONHASHSEED

如果需要稳定的散列实现,可能需要查看 hashlib模块; 它实现了加密散列函数。PyBloom 项目使用这种方法.

遗憾的是,由于偏移量由前缀和后缀(分别是开始值和最终 XORed 值)组成,因此不能只存储偏移量。从积极的一面来看,这确实意味着攻击者也不能轻易地通过定时攻击来确定偏移量。

散列随机化是 在 Python3中默认打开。这是一个安全特性:

散列随机化的目的是提供保护,以防止由于精心选择的输入而导致的拒绝服务,这些输入利用了 dict 结构的最坏情况性能

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

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

当我试图比较不同会话之间保存在数据库中的记录时,hash()的这种行为绊倒了我。

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

因此,我创建了一个 simple 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)