python的随机数生成容易重现吗?
Posted
技术标签:
【中文标题】python的随机数生成容易重现吗?【英文标题】:Is python's random number generation easily reproducible? 【发布时间】:2015-09-30 03:06:24 【问题描述】:我正在阅读有关标准库中 python 的随机模块的信息。让我惊讶的是,当我设置种子并产生一些随机数时:
random.seed(1)
for i in range(5):
print random.random()
产生的数字与文章中的样本完全相同。我认为可以肯定地说算法在设置种子时是确定性的。
并且当种子没有设置时,标准库种子使用time.time()
。
现在假设一个在线服务使用random.random()
生成验证码,黑客可以使用相同的随机生成器轻松复制验证码吗?
-
假设黑客知道将随机数转换为验证码的算法。否则,这似乎是不可能的。
由于在导入模块时调用了 random.seed(),我假设对于 Web 应用程序,用作种子的时间大约是发送请求的时间(几秒钟内),它不会是尝试几次就难以校准?
是我太担心了,还是这是一个真正的漏洞?
【问题讨论】:
【参考方案1】:播种后序列是确定性的,您应该不会感到惊讶。这就是播种的全部意义所在。 random.random
被称为 PRNG,一个伪-随机数生成器。这不是 Python 独有的,每种语言的简单随机源都是以这种方式确定的。
是的,真正关心安全的人会担心攻击者可能会重现该序列。这就是为什么可以使用其他随机源,例如 os.urandom
,但它们更昂贵。
但问题并没有你说的那么严重:对于一个 web 请求,通常一个进程会处理多个请求,因此该模块是在过去某个未知时间点初始化的,而不是在收到 web 请求时。
【讨论】:
【参考方案2】:现有的答案很好,但我只是补充几点。
更新:
实际上,如果您不提供种子,则随机数生成器会使用来自系统随机源的随机位作为种子,如果操作系统没有随机数,它只会回退到使用系统时间作为种子资源。另请注意,最新版本的 Python 可以使用改进的播种方案。来自the docs:
random.seed(a=None, version=2)
初始化随机数生成器。
如果省略
a
或None
,则使用当前系统时间。如果 随机源由操作系统提供,它们用于 而不是系统时间(请参阅os.urandom()
函数 可用性的详细信息)。如果
a
是一个int,则直接使用。对于版本 2(默认),str、bytes 或 bytearray 对象获取 转换为 int 并使用其所有位。
使用版本 1(提供用于从旧版本复制随机序列 Python 版本),str 和 bytes 的算法会生成一个 种子范围更窄。
在 3.2 版中更改:移至使用字符串种子中的所有位的版本 2 方案。
与生成加密密钥(尤其是打算多次使用的密钥)相比,生成验证码并不是一种高安全性应用程序。因此,生成 CAPTCHA 代码所需的熵量小于加密密钥所需的熵。
请记住,用于播种 random
的系统时间(可能)不是以秒为单位的系统时间 - 它更可能是以微秒甚至纳秒为单位的时间,因此攻击者不容易计算除了 Ned 提到的考虑因素之外,从蛮力搜索中取出种子。
这是一个快速演示,在 2GHz Linux 系统上运行 Python 2.6.6。
#!/usr/bin/env python
''' random seeding demo'''
from __future__ import print_function
import time
from random import seed, randint, random
def rf():
return randint(10, 99)
def put_time():
print('%.15f' % time.time())
r = range(10)
a = []
put_time()
for i in r:
seed()
a.append([rf() for j in r])
put_time()
for row in a:
print(row)
典型输出
1436617059.071794986724854
1436617059.074091911315918
[95, 25, 50, 75, 80, 38, 21, 26, 85, 82]
[75, 96, 14, 13, 76, 53, 94, 68, 80, 66]
[79, 33, 65, 86, 12, 32, 80, 83, 36, 42]
[28, 47, 62, 21, 52, 30, 54, 62, 22, 28]
[22, 40, 71, 36, 78, 64, 17, 33, 99, 43]
[81, 15, 32, 15, 63, 57, 83, 67, 12, 62]
[22, 56, 54, 55, 51, 56, 34, 56, 94, 16]
[64, 82, 37, 80, 70, 91, 56, 41, 55, 12]
[47, 37, 64, 14, 69, 65, 42, 17, 22, 17]
[43, 43, 73, 82, 61, 55, 32, 52, 86, 74]
如您所见,外循环开始和结束之间的时间不到 3 毫秒,但 a
中的所有列表都完全不同。
请注意,传递给random.seed()
的种子可以是任何可散列的对象,当你传递一个非整数(例如float
像系统时间)时,它首先被散列以创建一个整数。
不过,没有必要仅仅使用系统时间作为种子:您可以使用SystemRandom
/ os.urandom()
来获取种子。那样的话,种子更不可预测,但你得到梅森捻线机的速度; SystemRandom
比 Mersenne Twister 慢一点,因为它必须进行系统调用。然而,即使urandom
也不是完全安全的。
来自 GNU urandom man page:
随机数生成器从设备收集环境噪声 驱动程序和其他来源进入熵池。发电机也 保持对熵池中噪声位数的估计。 从这个熵池中创建随机数。
读取时,/dev/random 设备将只返回随机字节 在熵池中估计的噪声位数之内。 /dev/random 应该适合需要非常高质量的用途 随机性,例如一次性填充或密钥生成。当熵 池为空,从 /dev/random 读取将阻塞,直到额外 收集环境噪音。
从 /dev/urandom 设备读取不会阻塞等待更多 熵。因此,如果没有足够的熵 熵池,返回值理论上容易受到 对驱动程序使用的算法的加密攻击。知识 如何做到这一点在当前未分类中不可用 文献,但从理论上讲,这种攻击可能 存在。如果这是您的应用程序中的一个问题,请使用 /dev/random 而是。
用法
如果您不确定是否应该使用 /dev/random 或 /dev/urandom,那么您可能想使用后者。 作为一般规则,/dev/urandom 应该用于除 长期存在的 GPG/SSL/SSH 密钥。
有关为什么 /dev/urandom 几乎总是比 /dev/random 更可取的更多信息,请参阅Myths about /dev/urandom。
【讨论】:
感谢代码和参考!信息量很大。【参考方案3】:几乎所有的模块函数都依赖于基本函数random(),它在半开范围[0.0, 1.0)内均匀地生成一个随机浮点数。 Python 使用 Mersenne Twister 作为核心生成器。它产生 53 位精度浮点数,周期为 2**19937-1。 C 中的底层实现既快速又线程安全。 Mersenne Twister 是现有测试最广泛的随机数生成器之一。但是,由于是完全确定性的,它并不适合所有用途,并且完全不适合加密用途。
请参阅this answer 以获取安全随机数。
【讨论】:
【参考方案4】:Python documentation 有话要说:
警告 该模块的伪随机生成器不应用于 安全目的。如果需要,请使用 os.urandom() 或 SystemRandom 密码安全的伪随机数生成器。
因此,将其用于 CAPTCHA 可能不是一个好主意。
【讨论】:
以上是关于python的随机数生成容易重现吗?的主要内容,如果未能解决你的问题,请参考以下文章