【问题标题】:Hex digest for Python's built-in hash functionPython内置哈希函数的十六进制摘要
【发布时间】:2017-08-14 12:28:44
【问题描述】:

我需要从一组嵌套的配置值中创建一个标识符标记。 令牌可以是 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 表示,这很麻烦。

【问题讨论】:

  • 我倾向于做mask = (1<<sys.hash_info.width) - 1h = hash(config) & mask
  • 哦,聪明。可能不是所有最Pythonic-est的方式......
  • 这是一个使用小整数的原理演示:[i & 0xf for i in range(-8, 8)]。 FWIW,这是一个相当标准的 Python 习惯用法,用于将有符号整数转换为无符号整数。
  • 好的,谢谢。好吧,你可能是对的——如果你不应该使用 Python 为什么会有位运算符。
  • 这些哈希值是否打算在您的程序的单次运行之后使用?如果是这样,您不能使用内置的 hash() - 不能保证在所有 Python 版本中都以相同的方式计算,并且在某些时候,字符串哈希开始在每个程序运行时被有意地随机化。

标签: python hash hexdump


【解决方案1】:

您可以使用任何散列函数来获取唯一字符串。目前 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。但请注意,它不是哈希算法,您(以及任何知道 salt 的人)都可以解密数据。

$ pip install hashids

基本用法:

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

【讨论】:

  • 谢谢 - 我知道 hashlib 模块。不方便的是它只需要一个平坦的bytes 序列作为输入。在我的场景中,这意味着遍历配置结构并将每个值转换为某种bytes 表示,这很麻烦且容易出错。
  • 没错,但你不能将hash 用于某些类型,例如:list、dict、set,所以你需要像hashlib 模块一样转换这些结构。您可以手动查看>>>hash({})
  • 这就是为什么我在我的帖子(第一段)中写道:“配置值是嵌套的元组,其中包含 intboolstr 等可散列类型的元素。”注意hashable这个词。
【解决方案2】:

我需要从一组嵌套的配置值中创建一个标识符标记

我在尝试解决相同问题时遇到了这个问题,并意识到对 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:]

【讨论】:

    【解决方案3】:

    我推荐使用 hashlib

    将标记转换为字符串,然后将十六进制摘要转换为十六进制整数。 Bellow 是 sha256 算法的示例,但您可以使用 hashlib 支持的任何散列算法

    import hashlib as hl
    def shasum(token):
        return int(hl.sha256(str(token).encode('utf-8')).hexdigest(), 16)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-27
      • 2013-08-18
      • 2014-07-24
      • 2020-01-22
      • 2015-03-24
      相关资源
      最近更新 更多