从python程序中禁用散列随机化

ale*_*xis 29 python hash python-3.x

与Python 3.3开始,哈希算法是不确定性避免某种攻击.这对于Web服务器来说很好,但是在尝试调试程序时会很痛苦:每次运行脚本时,dict内容都会以不同的顺序迭代.

一些早期版本的python有一个-R用于启用散列随机化的标志,但现在它是默认行为,该标志尚未被其相反的替换.可以通过设置环境变量来禁用随机化PYTHONHASHSEED:

PYTHONHASHSEED

如果未将此变量设置或设置为random,则使用随机值为str,bytes和datetime对象的哈希值设定种子.
如果将PYTHONHASHSEED设置为整数值,则将其用作固定种子,以生成散列随机化所涵盖类型的散列().

问题是必须在启动python进程之前设置此变量.我试着与设置os.putenv(),或在os.environ,但这些似乎都对散列方法没有效果.这并不太令人惊讶:我不希望python在每一组或字典查找之前检查环境!所以,问题仍然存在:

有没有办法让python程序禁用自己的哈希随机化?

dim*_*414 16

不幸的是,我怀疑这是不可能的.综观test_hash.pyHashRandomizationTests类,并在加入其后代犯介绍了这种行为.他们通过修改环境并使用PYTHONHASHSEED显式设置启动新进程来测试散列行为.您可以尝试复制该模式.

我也注意到你说" 每次我运行我的脚本时,dict内容都会以不同的顺序迭代. " - 我假设你知道collections.OrderedDict,对吧?这是获得可靠的哈希迭代的常规方法.


如果您愿意在shell环境中设置值,您也可以将您的python调用包装在bash脚本中,例如

#! /bin/bash
export PYTHONHASHSEED=0

# call your python program here
Run Code Online (Sandbox Code Playgroud)

这样就可以避免需要操作整个环境,只要您使用包装器脚本即可.

或者甚至只是在命令行上传递值:

$ PYTHONHASHSEED=0 python YOURSCRIPT.py
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,这是一个非常强烈的迹象。还有一个很好的重生提示——尽管除了丑陋之外,还有一些不实用的上下文(例如,如果在由远程“内核”提供服务的 ipython 笔记本中运行)。我想我可以在登录时为我的环境设置它......我自己不会做 DoS。 (2认同)

Joa*_*ner 6

除了字典顺序,散列随机化也可能破坏hash()直接使用的现有代码。在这种情况下为我解决问题的解决方法是更换

hash(mystring)

int(hashlib.sha512(mystring).hexdigest(), 16)

对于 Python 3,mystring.encode('utf-8')标准字符串需要类似的转换。(我正在处理字节字符串。)

注意数字的范围和是否包含负数是不同的。后一种代码提供了更大范围的数字,并且哈希冲突极不可能。

要重现与 相同的 64 位范围hash(),可以将十六进制数字的数量减少到 16(每位 4 位)并将结果移位到最小的负 64 位数字开始:

int(hashlib.sha256(mystring).hexdigest()[:16], 16)-2**63

或者,可以使用 8 个字节并使用int.from_bytes

int.from_bytes(hashlib.sha256(mystring).digest()[:8], byteorder='big', signed=True)


use*_*175 5

也许唯一/最干净的方法是将其添加到程序的开头:

import os
import sys
hashseed = os.getenv('PYTHONHASHSEED')
if not hashseed:
    os.environ['PYTHONHASHSEED'] = '0'
    os.execv(sys.executable, [sys.executable] + sys.argv)

[the rest of your program]
Run Code Online (Sandbox Code Playgroud)

如果PYTHONHASHSEED缺少,它会将其设置为零并用新程序替换当前程序,并提供相同的参数集。根据os.execv

这些函数都执行一个新的程序,替换当前的进程;他们不回来。在 Unix 上,新的可执行文件被加载到当前进程中,并且与调用者具有相同的进程 ID。错误将报告为 OSError 异常。

当前进程立即被替换。打开的文件对象和描述符不会被刷新,所以如果这些打开的文件上可能有缓冲的数据,你应该在调用 exec* 函数之前使用 sys.stdout.flush() 或 os.fsync() 刷新它们。