random.choice 不随机

16 投票
5 回答
10497 浏览
提问于 2025-04-15 14:01

我在Linux上使用Python 2.5,并且在多个并行的FCGI进程中工作。我使用

    chars = string.ascii_letters + string.digits
    cookie = ''.join([random.choice(chars) for x in range(32)])

来生成不同的cookie。假设随机数生成器是从/dev/urandom获取种子的,并且随机数的序列是来自梅森旋转算法,我本以为碰撞的可能性几乎为零。

然而,我确实看到经常发生碰撞,尽管同时登录的用户只有少于100个。

为什么这些随机数没有更随机呢?

5 个回答

1
  1. 我不知道你的FCGI进程是怎么启动的,但有可能是在Python解释器启动后使用了fork(),而且在这之前随机模块已经被某个东西导入了,这样就导致两个进程的random._inst是从同一个源头生成的。

  2. 也许可以加一些调试代码,检查一下它是否正确地从urandom获取种子,而不是退回到不那么严格的基于时间的种子?

补充评论:哎呀!这让我很困惑;如果随机数生成器在启动时总是有不同的状态,我就看不出怎么会出现冲突。真奇怪。可能需要记录很多状态信息来调查导致冲突的具体情况,这听起来像是要花很多时间去翻阅日志。可能是(1a)FCGI服务器通常不使用fork,但偶尔会在负载较高时使用?

或者(3)是一些更高层次的问题,比如一个坏的HTTP代理把同一个Set-Cookie发送给多个客户端?

4

这绝对不是一个正常的碰撞情况:

  • 32个字符,每个字符有62种选择,这相当于190位(计算方式是log2(62) * 32)
  • 根据生日悖论,你应该每2的95次方的“饼干”中自然会遇到一次碰撞,这意味着几乎不可能发生

这可能是并发问题吗?

  • 如果是的话,建议每个线程使用不同的random.Random实例
  • 可以将这些实例保存在线程本地存储中(threading.local()
  • 在Linux系统上,Python应该使用os.urandom()来初始化这些实例,而不是系统时间,这样每个线程就能得到不同的随机数流。
13

不应该生成重复的内容。

import random
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def gen():
    return ''.join([random.choice(chars) for x in range(32)])

test = [gen() for i in range(100000)]
print len(test), len(set(test)) # 100000 100000

如果字符集是"ab",那么在1000000次尝试中,重复的可能性很大,达到了126次重复。但是如果字符集是62,就几乎没有重复的情况。

不过,这种方法并不适合用来生成会话cookie,因为会话cookie需要是不可预测的,这样才能防止有人偷取其他人的会话cookie。梅森旋转算法并不是为了生成安全的随机数而设计的。我通常会这样做:

import os, hashlib
def gen():
    return hashlib.sha1(os.urandom(512)).hexdigest()

test = [gen() for i in range(100000)]
print len(test), len(set(test))

... 这样应该是非常安全的(也就是说,从一串会话cookie中很难猜测出其他已经存在的会话cookie)。

撰写回答