【问题标题】:extract hash seed in unit testing在单元测试中提取哈希种子
【发布时间】:2016-12-11 16:54:43
【问题描述】:

我需要获取 python 用来复制 失败 单元测试的随机哈希种子。

如果PYTHONHASHSEED 设置为非零整数,sys.flags.hash_randomization 会可靠地提供它:

$ export PYTHONHASHSEED=12345
$ python3 -c 'import sys, os;print(sys.flags.hash_randomization, os.environ.get("PYTHONHASHSEED"))'
12345 12345

但是,如果散列是随机的,它只说明 使用了种子,而不是:

$ export PYTHONHASHSEED=random
$ python3 -c 'import sys, os;print(sys.flags.hash_randomization, os.environ.get("PYTHONHASHSEED"))'
1 random

sys.hash_info 中的信息从不包含取决于种子的数据。使用hash function since python3.4,尝试从给定的哈希重建种子似乎也不可行。


上下文:在微调算法时,我们已经看到了依赖于 set/dict 迭代顺序的 heisenbugs。复制它们需要测试种子,最坏的情况是全部 4294967295,但即使我们平均约 100 次测试也相当冗长。

我们一直考虑在外部将 PYTHONHASHSEED 设置为随机但已知的值,但希望避免这个额外的层。

【问题讨论】:

    标签: python python-3.x unit-testing hash


    【解决方案1】:

    不,随机值被分配给_Py_HashSecret unionuc 字段,但这绝不会暴露给 Python 代码。这是因为可能值的数量远远大于设置PYTHONHASHSEED 所能产生的数量。

    当您不设置PYTHONHASHSEED 或将其设置为random 时,Python 会生成一个随机的 24 字节值用作种子。如果将PYTHONHASHSEED 设置为整数,则该数字将通过linear congruential generator 传递以生成实际种子(请参阅lcg_urandom() function)。问题是PYTHONHASHSEED 仅限于 4 个字节。与单独通过PYTHONHASHSEED 设置的种子值相比,可能的种子值多 256 ** 20 倍。

    可以使用ctypes 访问_Py_HashSecret 结构中的内部哈希值:

    from ctypes import (
        c_size_t,
        c_ubyte,
        c_uint64,
        pythonapi,
        Structure,
        Union,
    )
    
    
    class FNV(Structure):
        _fields_ = [
            ('prefix', c_size_t),
            ('suffix', c_size_t)
        ]
    
    
    class SIPHASH(Structure):
        _fields_ = [
            ('k0', c_uint64),
            ('k1', c_uint64),
        ]
    
    
    class DJBX33A(Structure):
        _fields_ = [
            ('padding', c_ubyte * 16),
            ('suffix', c_size_t),
        ]
    
    
    class EXPAT(Structure):
        _fields_ = [
            ('padding', c_ubyte * 16),
            ('hashsalt', c_size_t),
        ]
    
    
    class _Py_HashSecret_t(Union):
        _fields_ = [
            # ensure 24 bytes
            ('uc', c_ubyte * 24),
            # two Py_hash_t for FNV
            ('fnv', FNV),
            # two uint64 for SipHash24
            ('siphash', SIPHASH),
            # a different (!) Py_hash_t for small string optimization
            ('djbx33a', DJBX33A),
            ('expat', EXPAT),
        ]
    
    
    hashsecret = _Py_HashSecret_t.in_dll(pythonapi, '_Py_HashSecret')
    hashseed = bytes(hashsecret.uc)
    

    但是,您实际上无法使用此信息进行任何操作。您不能在新的 Python 进程中设置 _Py_HashSecret.uc,因为这样做会破坏大多数字典键集,然后才能从 Python 代码中设置(Python 内部严重依赖字典),并且您的哈希值等于其中之一256**4 个可能的 LCG 值非常小。

    您将PYTHONHASHSEED 设置为任何地方的已知值的想法是一种更为可行的方法。

    【讨论】:

    • 您可以通过这种方式检测 PYTHONHASHSEED 是否设置为 0。
    • @Mitar 通过sys.flags.hash_randomization(在这种情况下设置为0)或查看os.environ.get("PYTHONHASHSEED") 更容易检测到。
    猜你喜欢
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 2011-10-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多