Python内置哈希函数的十六进制摘要

9
我需要从一组嵌套的配置值中创建标识令牌。该令牌可以成为URL的一部分,因此为了使处理更容易,它应只包含十六进制数字(或类似的内容)。配置值是嵌套元组,其元素为可哈希类型,例如intboolstr等。
我的想法是使用内置的hash()函数,因为即使配置结构改变,它仍将继续工作。这是我的第一次尝试:
def token(config):
    h = hash(config)
    return '{:X}'.format(h)

这将产生长度可变的令牌,但这并不重要。然而,令我困扰的是令牌可能包含一个负号,因为hash()的返回值是有符号整数。 为了避免符号,我想到了以下解决方法,即向哈希值添加一个常量。 这个常量应该是取值范围的一半,hash()的取值范围取决于平台(例如32位/64位系统)。
HALF_HASH_RANGE = 2**(sys.hash_info.width-1)

这是一个明智且可移植的解决方案吗?还是说我会被它绊倒?

我还看到有人建议使用struct.pack()(它返回一个bytes对象,可以在其上调用.hex()方法),但它也需要预先知道哈希值的范围(以选择正确的格式字符)。

补充说明:
加密强度或偶然碰撞都不是问题。 hashlib库在此场景中的缺点是需要编写转换器来遍历输入结构并将所有内容转换为bytes表示形式,这很繁琐。


1
我倾向于执行 mask = (1<<sys.hash_info.width) - 1 h = hash(config) & mask - PM 2Ring
这是一个使用小整数演示原理的示例:[i&0xf for i in range(-8,8)]。顺便说一句,这是将有符号整数转换为无符号整数的相当标准的Python习语。 - PM 2Ring
好的,谢谢。嗯,你可能是对的 - 如果不应该使用位运算符,为什么Python会有它们呢。 - lenz
现在这是一个绝妙的想法 - 当然,我可以只调用repr()来序列化整个结构!为什么我之前没有想到呢... - lenz
2
这些哈希值是否打算在程序的单次运行之外使用?如果是这样,你不能使用内置的hash()函数——它不能保证在所有的Python版本中都以相同的方式计算,并且在某个时候,字符串哈希开始有意地在每个程序运行时随机化。 - jasonharper
显示剩余5条评论
3个回答

4
你可以使用任何哈希函数来获取唯一的字符串。现在,Python支持许多算法,如:md5、sha1、sha224、sha256、sha384、sha512。你可以在这里了解更多信息 - https://docs.python.org/2/library/hashlib.html 以下是如何使用hashlib库的示例。(Python 3)
>>> import hashlib
>>> sha = hashlib.sha256()
>>> sha.update('somestring'.encode())
>>> sha.hexdigest()
>>> '63f6fe797026d794e0dc3e2bd279aee19dd2f8db67488172a644bb68792a570c'

您可以尝试使用库hashids。但请注意,它不是哈希算法,您和任何知道盐值的人都可以解密数据。
$ pip install hashids

基本用法:

>>> from hashids import Hashids
>>> hashids = Hashids()
>>> hashids.encode(123)
'Mj3'
>>> hashids.decode('Mj3')
123

1
谢谢 - 我知道 hashlib 模块。不方便的是,它只接受一个扁平的 bytes 序列作为输入。在我的情况下,这意味着遍历配置结构并将每个值转换为某些 bytes 表示形式,这很麻烦且容易出错。 - lenz
没错,但您不能对某些类型使用hash,例如:列表、字典、集合,因此您需要将这些结构转换为hashlib模块的类型。您可以手动检查它 >>> hash({}) - Mentos
1
这就是为什么我在我的帖子中写道(第一段):“配置值是嵌套元组,其中包含可哈希类型的元素,如intboolstr等。”请注意可哈希这个词。 - lenz

1
我需要从一组嵌套的配置值中创建一个标识符令牌。
在尝试解决相同问题时,我遇到了这个问题,并意识到某些对hash的调用返回负整数。
下面是我实现token函数的方式:
import sys


def token(config) -> str:
    """Generates a hex token that identifies a hashable config."""
    # `sign_mask` is used to make `hash` return unsigned values
    sign_mask = (1 << sys.hash_info.width) - 1
    # Get the hash as a positive hex value with consistent padding without '0x'
    return f'{hash(config) & sign_mask:#0{sys.hash_info.width//4}x}'[2:]

在我的情况下,我需要它能够处理各种配置输入。它不需要特别高的性能(因为它不在热路径上),如果偶尔发生冲突(比hash通常预期的要多),也是可以接受的。它真正需要做的就是为一致的输入生成短(例如16个字符长)且一致的输出。因此,在我的情况下,我对以上函数进行了小修改以确保提供的配置是可哈希的,这样做会增加冲突风险和处理时间的代价。
import sys


def token(config) -> str:
    """Generates a hex token that identifies a config."""
    # `sign_mask` is used to make `hash` return unsigned values
    sign_mask = (1 << sys.hash_info.width) - 1
    # Use `json.dumps` with `repr` to ensure the config is hashable
    json_config = json.dumps(config, default=repr)
    # Get the hash as a positive hex value with consistent padding without '0x'
    return f'{hash(json_config) & sign_mask:#0{sys.hash_info.width//4}x}'[2:]

-1
我建议使用hashlib。
将令牌转换为字符串,然后将hexdigest转换为十六进制整数。下面是一个使用sha256算法的示例,但您可以使用hashlib支持的任何哈希算法。
import hashlib as hl
def shasum(token):
    return int(hl.sha256(str(token).encode('utf-8')).hexdigest(), 16)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接