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)

初始化随机数生成器。

如果省略aNone,则使用当前系统时间。如果 随机源由操作系统提供,它们用于 而不是系统时间(请参阅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的随机数生成容易重现吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中生成可重现(带有种子)的随机 UUID

Python深度学习之路产生随机数

Python深度学习之路产生随机数

Python深度学习之路产生随机数

Python中有用于生成随机字符串的模块吗? [复制]

Python生成UUID