【问题标题】:Python and random keys of 21 char maxPython和最大21个字符的随机键
【发布时间】:2010-10-11 22:06:58
【问题描述】:

我正在使用一个名称为 21 char max 的 api 来表示一个内部会话,该会话的生命周期约为“两天”。我希望使用某种 hasing 的名称没有意义? md5 生成 40 个字符,还有什么我可以使用的吗?

现在我使用 'userid[:10]' + 创建时间:ddhhmmss + 随机 3 个字符。

谢谢,

【问题讨论】:

  • 你一定在想 SHA1。 MD5 是 32 位十六进制数字。

标签: python encryption key


【解决方案1】:

如果我正确阅读了您的问题,您想生成一些任意标识符令牌,该令牌最多必须为 21 个字符。它是否需要高度抗猜测?您给出的示例不是“密码学强”,因为它可以通过搜索不到整个可能键空间的 1/2 来猜测。

您没有说这些字符是否可以全部是 256 个 ASCII 字符,或者是否需要限制为可打印的 ASCII(33-127,包括在内)或更小的范围。

有一个为UUIDs(通用唯一标识符)设计的 Python 模块。您可能需要 uuid4 来生成随机 UUID,并在可用的情况下使用操作系统支持(在 Linux、Mac、FreeBSD 和可能的其他系统上)。

>>> import uuid
>>> u = uuid.uuid4()
>>> u
UUID('d94303e7-1be4-49ef-92f2-472bc4b4286d')
>>> u.bytes
'\xd9C\x03\xe7\x1b\xe4I\xef\x92\xf2G+\xc4\xb4(m'
>>> len(u.bytes)
16
>>> 

16 个随机字节非常难以猜测,如果您只想拥有一个不可猜测的不透明标识符,则无需使用 API 允许的全部 21 个字节。

如果你不能使用这样的原始字节,这可能是个坏主意,因为它更难在日志和其他调试消息中使用,也更难用肉眼比较,然后将字节转换成更易读的东西,比如使用 base-64 编码,结果减少到 21(或其他)字节:

>>> u.bytes.encode("base64")
'2UMD5xvkSe+S8kcrxLQobQ==\n'
>>> len(u.bytes.encode("base64")) 
25
>>> u.bytes.encode("base64")[:21]
'2UMD5xvkSe+S8kcrxLQob'
>>> 

这为您提供了一个长度为 21 的极高质量随机字符串。

您可能不喜欢可以在 base-64 字符串中的“+”或“/”,因为没有适当的转义可能会干扰 URL。由于您已经考虑使用“随机 3 个字符”,我认为这不是您的担心。如果是,您可以将这些字符替换为其他字符('-' 和 '.' 可能会起作用),或者如果存在则删除它们。

正如其他人指出的那样,您可以使用 .encode("hex") 并获得等效的十六进制,但这只是 4 位随机性/字符 * 最多 21 个字符为您提供 84 位随机性,而不是两倍。每一位都会使您的键空间翻倍,从而使理论搜索空间变得非常小。小 2E24 倍。

即使使用十六进制编码,您的密钥空间大小仍然是 2E24,所以我认为这更多是理论上的问题。我不会担心有人对你的系统进行暴力攻击。

编辑

P.S.:uuid.uuid4 函数使用 libuuid(如果可用)。它的熵来自 os.urandom(如果可用),否则来自当前时间和本地以太网 MAC 地址。如果 libuuid 不可用,则 uuid.uuid4 函数直接从 os.urandom (如果可用)获取字节,否则它使用 random 模块。 random 模块使用基于 os.urandom (如果可用)的默认种子,否则使用基于当前时间的值。每个函数调用都会进行探测,因此如果您没有 os.urandom,那么开销会比您预期的要大一些。

把消息带回家?如果你知道你有 os.urandom 那么你可以这样做

os.urandom(16).encode("base64")[:21]

但如果您不想担心它的可用性,请使用 uuid 模块。

【讨论】:

  • 我忘了补充应该是 url 安全的,我应该在三个随机字符中指定它。我将使用您的方法并替换 + 和 / 字符。
  • 我找到了一个 uri_b64encode 安全方法,可以很好地完成这项工作,谢谢
  • 请注意,UUID4 并不完全为您提供 16 个随机字节。有 6 个固定(非随机)位。当然,这已经足够了。
  • 真的吗?我查看了 uuid.py,它似乎给出了 16 个随机字节……啊哈!构造函数根据版本号做了一些摆弄。那是我所看到的下游。感谢 kmkaplan 的更正。
  • 对于为什么会这样感兴趣的人,请查看 RFC 4122 的第 4.4 节:ietf.org/rfc/rfc4122.txt
【解决方案2】:

MD5 的十六进制表示具有非常差的随机性:每个字符只能得到 4 位熵。

使用随机字符,例如:

import random
import string
"".join([random.choice(string.ascii_letters + string.digits + ".-")
        for i in xrange(21)])

在选项中输入所有可接受的字符。

虽然使用诸如 SHA1 之类的真正散列函数也会获得不错的结果如果使用得当,增加的复杂性和 CPU 消耗似乎不符合您的需求。你只想要一个随机字符串。

【讨论】:

  • string.ascii_letters,因为 string.letters 取决于语言环境。
【解决方案3】:

为什么不从 md5 或 SHA1 哈希中取出前 21 个字符?

【讨论】:

  • 确实应该足够随机
  • 类似 hashlib.md5(str(random.random())).hexdigest()[:21]
  • random.random() 默认从 os.urandom 获取种子,否则从 time.time 获取。假设操作系统支持 os.urandom,不妨做 os.urandom(11).encode("hex")[:21] 。
【解决方案4】:

base64 模块可以进行 URL 安全编码。所以,如果需要,而不是

u.bytes.encode("base64")

你可以的

import base64

token = base64.urlsafe_b64encode(u.bytes)

并且方便地转换回来

u = uuid.UUID(bytes=base64.urlsafe_b64decode(token))

【讨论】:

    【解决方案5】:

    字符还是字节?如果它需要任意字符串,您可以只使用字节而不用担心扩展为可读字符(无论如何,base64 比十六进制更好)。

    如果您不使用它的十六进制扩展,MD5 会生成 16 个字符。 SHA1在相同条件下生成20。

    >>> import hashlib
    >>> len(hashlib.md5('foobar').digest())
    16
    >>> len(hashlib.sha1('foobar').digest())
    20
    

    之后需要很少的额外字节。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-10
      • 1970-01-01
      • 2018-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多