Python中的唯一会话ID

50

如何在Python中生成唯一的会话ID?


1
Python 3.6+ 的正确答案是:https://dev59.com/j3RA5IYBdhLWcg3wzhXZ#55661405 - Agost Biro
5个回答

79

更新:2016-12-21

在过去的约5年里发生了很多事情。 /dev/urandom 已经更新,并且在现代Linux内核和发行版上被认为是高熵随机性源。在最近的6个月中,我们在使用Ubuntu的Linux 3.19内核上看到了熵饥饿问题,所以我认为这个问题并没有“解决”,但是从操作系统请求任意数量的随机数时很容易出现低熵随机性。


我不想这么说,但是这里发布的其他解决方案都与“安全会话ID”的正确性无关。

# pip install M2Crypto
import base64, M2Crypto
def generate_session_id(num_bytes = 16):
    return base64.b64encode(M2Crypto.m2.rand_bytes(num_bytes))

uuid()os.urandom()都不是生成会话ID的好选择。尽管两者都可以产生随机结果,但随机并不意味着它是安全的,因为熵不足。请参阅Haldir的"如何破解线性同余生成器"或NIST关于随机数生成的资源。如果您仍然希望使用UUID,则使用使用良好初始随机数生成的UUID:

import uuid, M2Crypto
uuid.UUID(bytes = M2Crypto.m2.rand_bytes(num_bytes)))
# UUID('5e85edc4-7078-d214-e773-f8caae16fe6c')
或者:
# pip install pyOpenSSL
import uuid, OpenSSL
uuid.UUID(bytes = OpenSSL.rand.bytes(16))
# UUID('c9bf635f-b0cc-d278-a2c5-01eaae654461')

M2Crypto是目前Python中最好的OpenSSL API,因为pyOpenSSL似乎只维护以支持旧应用程序。


那些关于UUID问题的引用很有帮助。感谢您发布这个。问题是:您认为生成会话ID的最佳方法是什么?特别是在UUID实现中存在的缺陷,您会如何做出不同的选择?我正在编写类似于此的东西,并尝试提出最佳方法。它还必须具有容错性-例如,不能依赖于与数据库服务器的连接。 - ratsbane
21
如果我们剥离所有的糟粕,你基本上是在说 OpenSSL.rand.bytes(16) 是安全的,但 os.urandom(16) 不安全。根据文档,os.urandom 的目的是“返回一个由n个随机字节组成的字符串,适合用于密码学”。如果生成一个良好的会话 ID 不是 os.urandom 适合的“密码学用途”,那它到底是用来做什么的呢?也许正确的解决方案对你来说太简单了,但这就是 Python。无意义的废话并不能让事情更加安全。 - Seun Osewa
6
@SeunOsewa,你关于文档和os.urandom适用于加密用途的观点是正确的,但不幸的是,并非总是如此。FreeBSD和OS-X有一个很好的urandom池,Linux则好坏参半(虽然越来越好)。显式比隐式更好。顺便说一句,我发布这篇文章的原因是在现实世界情况下遇到了会话ID冲突,而没有检查会话ID冲突的用户可以看到彼此的信息。原因是urandom没有被适当地种子化。 :-/ 现实有时候令人沮丧。 - Sean
3
你说得对,OpenSSL和os.urandom都使用相同的熵源(/dev/urandom),并具有相同的安全级别。 - ramirami
3
一项初步评估:你声称os.urandom不足以提供足够的随机性以保证安全性,但 OpenSSL(例如通过 M2Crypto)更好。与此同时,@ramirami 声称(同样没有证据)实际上两者使用相同的基础熵源。我不知道谁是对的,但我仍然会点踩;我不喜欢 FUD,这里的大胆声明(即 os.urandom 在某些平台上使用比 OpenSSL 更差的熵源,以至于在后者安全的情况下前者在密码学上已经被破解)需要提供证明才能有用。 - Mark Amery
显示剩余3条评论

35

Python 3.6及以上版本包括secrets模块,专门为此目的设计,因此大多数其他答案已经有些过时了。如果您需要在Web上为任何目的生成加密安全的字符串,请参考该模块。

https://docs.python.org/3/library/secrets.html

示例:

import secrets

def make_token():
    """
    Creates a cryptographically-secure, URL-safe string
    """
    return secrets.token_urlsafe(16)  

使用中:

>>> make_token()
'B31YOaQpb8Hxnxv1DXG6nA'

我是否应该在这个令牌中添加一个HMAC?以防止人们劫持会话? - Rockstar5645

29
您可以像这样使用uuid库
import uuid
my_id = uuid.uuid1() # 或者 uuid.uuid4()

@Gumbo:uuid将使用计算机的MAC地址和正常运行时间等信息来生成随机uuid,为什么这不是随机的? - Sverre Rabbelier
8
uuid1()、uuid4() 甚至 uuid5() 都不是好的会话 ID。请参考 https://dev59.com/j3RA5IYBdhLWcg3wzhXZ#6092448 获取一个安全的会话 ID 示例。 - Sean
2
维基百科上说,“版本4 UUID使用仅依赖于随机数的方案。”,那么为什么它不适合用作会话令牌?UUID5和UUID1不是基于随机数的,但为什么UUID4不好呢? - Buddy
1
UUID不使用加密安全的随机数生成器,因此不适用于生成安全的会话ID。Python 3.6+的正确答案是:https://dev59.com/j3RA5IYBdhLWcg3wzhXZ#55661405 - Agost Biro

23
import os, base64
def generate_session():
    return base64.b64encode(os.urandom(16))

2
我不确定,但这似乎是一个有效的解决方案。不过,我建议您删除末尾的“==”,并包括一个时间戳,以减少发生冲突的可能性。 - Unknown
1
4十亿次迭代后发生碰撞的概率是80亿分之1。如果我想进一步降低碰撞的概率,只需增加比特数,例如os.urandom(32)。而我不明白去除尾部的“==”有什么作用。 - Seun Osewa
1
末尾的“==”可以删除以节省空间。要解码它,您只需将其填充回最高的4的倍数即可。使用urandom可能会获得非常低的熵并最终导致重复。使用时间戳更好。 - Unknown
真的吗?(我不知道为什么一个4字节的时间戳比额外的4字节随机数更好,但)如果你所说的低熵是真的,那么我会选择自增的session_id,因为可能会在大约相同的时间内发出许多会话请求。即使在低熵情况下,我也不认为urandom会返回重复的32字节字符串。 伪随机算法可能会受到攻击,但它们不会返回重复的32字节序列。 - Seun Osewa
4
我认为这是满足我的需求的最佳解决方案。我尝试过 M2Crypto 和 PyCrypto,但两者在安装和在 Windows 上运行时都存在重大问题。 - Shwetanka
显示剩余2条评论

1

可以简单地创建一个随机数。当然,你需要将会话 ID 存储在数据库中,并检查每个生成的 ID 是否重复,但如果数字足够大,那么很可能不会出现重复的情况。


恰恰相反,这就是我的解决方案: https://dev59.com/j3RA5IYBdhLWcg3wzhXZ#818040 - Seun Osewa

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