Python和随机密钥的最大长度为21个字符。

7

我正在使用一个API,它接受最多21个字符的名称来表示一个内部会话,其寿命大约为"两天"。我希望使用某种哈希方式使名称不具备意义?MD5生成40个字符,有其他可用的方法吗?

目前,我使用'user id[:10]' + 创建时间:ddhhmmss + 随机3个字符。

谢谢。


你一定在想SHA1。MD5是32个十六进制数字。 - kmkaplan
5个回答

23
如果我理解你的问题正确,你想生成一个最多只能有21个字符的任意标识符令牌。它需要高度抵御猜测吗?你给出的示例不是“密码学强”,因为可以通过搜索远少于整个可能密钥空间的内容来猜测它。
你没有说字符是否可以是所有256个ASCII字符,或者是否需要限制为可打印的ASCII(33-127,包括在内)或某个更小的范围。
有一个专门设计用于UUID(通用唯一标识符)的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安全的,我应该在三个随机字符中指定它。我将使用您的方法并替换+和/字符。 - coulix
我找到了一个安全的uri_b64encode方法,它可以很好地完成工作,谢谢。 - coulix
请注意,UUID4并不完全给出16个随机字节。其中有6个固定(非随机)位。当然,这已经足够了。 - kmkaplan
真的吗?我看了uuid.py,它似乎会给出16个随机字节...啊哈!构造函数根据版本号进行一些位操作。那是我查看的下游。感谢kmkaplan的更正。 - Andrew Dalke
对于那些感兴趣的人,可以查看RFC 4122的4.4节:http://www.ietf.org/rfc/rfc4122.txt,了解其中的原因。 - David Sanders
使用base64.urlsafe_b64encode(uuid.uuid4().bytes)[:21]来生成URL安全的字符串。需要导入import base64模块。 - andilabs

4

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是与地区相关的。 - Andrew Dalke

2
基于 base64 模块可以进行 URL 安全编码。如果需要,可以使用 "urlsafe_b64encode" 方法来替代标准的 "b64encode" 方法。
u.bytes.encode("base64")

你可以做的事情
import base64

token = base64.urlsafe_b64encode(u.bytes)

并且,方便地,可以转换回去。
u = uuid.UUID(bytes=base64.urlsafe_b64decode(token))

2
为什么不从MD5或SHA1哈希值中取前21个字符呢?

确实,这应该足够随机了。 - coulix
类似这样的程序代码:hashlib.md5(str(random.random())).hexdigest()[:21] - S.Lott
random.random() 默认从 os.urandom 获取种子,否则从 time.time 获取。 假设 OS 支持 os.urandom,则最好执行 os.urandom(11).encode("hex")[:21]。 - Andrew Dalke

0

字符还是字节?如果它接受任意字符串,你可以直接使用字节而不必担心扩展到可读字符(对于这种情况,base64比十六进制更好)。

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

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

在此之后需要几个额外的字节。


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