Python的SystemRandom/os.urandom是否总有足够的熵用于良好的加密?

23 投票
3 回答
7903 浏览
提问于 2025-04-16 14:41

我有一个密码生成器:

import random, string

def gen_pass():
    foo = random.SystemRandom()
    length = 64
    chars = string.letters + string.digits
    return ''.join(foo.choice(chars) for _ in xrange(length))

根据文档,SystemRandom 使用 os.urandom,而 os.urandom 又是通过 /dev/urandom 来生成随机的加密位。在 Linux 系统中,你可以从 /dev/urandom/dev/random 获取随机位,它们都依赖于内核能获取的随机性。你可以通过运行 tail /proc/sys/kernel/random/entropy_avail 来检查可用的随机性,这个命令会返回一个数字,比如:129。数字越高,表示可用的随机性越多。/dev/urandom/dev/random 的区别在于,/dev/random 只有在 entropy_avail 足够高(比如至少 60)时才会输出随机位,而 /dev/urandom 则会一直输出随机位。文档中提到,/dev/urandom 适合用于加密,而 /dev/random 主要用于 SSL 证书等情况。

我的问题是,gen_pass 生成的密码是否总是能达到强加密的标准?如果我尽可能快地调用这个函数,是否会因为随机性池耗尽而导致生成的密码不再强大?

另一个问题是,为什么 /dev/urandom 总是能生成强加密位,而不在乎 entropy_avail 的值?

有可能 /dev/urandom 的设计是让它的输出速度受到你能猜测的随机性数量的限制,但这只是猜测,我找不到确切的答案。

另外,这是我第一次在 StackOverflow 上提问,请给我一些反馈。我担心我提供了太多背景信息,而那些知道答案的人可能已经了解这些背景。

谢谢

更新

我写了一些代码来观察在读取 /dev/urandom 时随机性池的情况:

import subprocess
import time

from pygooglechart import Chart
from pygooglechart import SimpleLineChart
from pygooglechart import Axis

def check_entropy():
    arg = ['cat', '/proc/sys/kernel/random/entropy_avail']
    ps = subprocess.Popen(arg,stdout=subprocess.PIPE)
    return int(ps.communicate()[0])

def run(number_of_tests,resolution,entropy = []):
    i = 0
    while i < number_of_tests:        
        time.sleep(resolution)
        entropy += [check_entropy()]
        i += 1
    graph(entropy,int(number_of_tests*resolution))

def graph(entropy,rng):    
    max_y = 200    
    chart = SimpleLineChart(600, 375, y_range=[0, max_y])
    chart.add_data(entropy)
    chart.set_colours(['0000FF'])
    left_axis = range(0, max_y + 1, 32)
    left_axis[0] = 'entropy'
    chart.set_axis_labels(Axis.LEFT, left_axis)    
    chart.set_axis_labels(Axis.BOTTOM,['time in second']+get_x_axis(rng))
    chart.download('line-stripes.png')

def get_x_axis(rng):
    global modnum        
    if len(filter(lambda x:x%modnum == 0,range(rng + 1)[1:])) > 10:
        modnum += 1
        return get_x_axis(rng)
    return filter(lambda x:x%modnum == 0,range(rng + 1)[1:])

modnum = 1
run(500,.1)

如果运行这个代码,同时再运行:

while 1 > 0:
    gen_pass()

那么我几乎可以可靠地得到一个这样的图表: enter image description here

在运行 cat /dev/urandom 时生成的图表看起来相似,而 cat /dev/random 很快就会降到零并保持在低位(这个命令大约每 3 秒只读取一个字节)

更新

如果我运行相同的测试,但用六个实例的 gen_pass(),我得到的是: enter image description here

所以看起来有某种机制确保我有足够的随机性。我应该测量一下密码生成的速度,确保它确实被限制住了,因为如果没有限制,那可能就有问题了。

更新

我找到了一封 邮件链

这封邮件说,urandom 一旦随机性池只有 128 位时就会停止提取随机性。这与之前的结果非常一致,意味着在那些测试中,我生成的密码可能经常是无效的。

我之前的假设是,如果 entropy_avail 足够高(比如超过 64 位),那么 /dev/urandom 的输出就是好的。但现在看来,/dev/urandom 的设计是为了留出额外的随机性给 /dev/random,以防它需要使用。

现在我需要找出一个 SystemRandom 调用需要多少真正的随机位。

3 个回答

-1

你可能想看看这个链接,了解一下为什么使用 /dev/urandom 是个好选择:

http://www.2uo.de/myths-about-urandom/

2

/dev/random/ 在读取时如果需要更多的随机性数据会让你等着。/dev/urandom/ 则不会这样。所以,如果你用得太快,可能会导致随机性数据不够。虽然这样猜测出来的内容还是很难,但如果你真的很在意的话,可以选择从 /dev/random/ 读取数据。理想情况下,应该使用一个非阻塞的读取循环,并加上一个进度指示器,这样你就可以移动鼠标来生成随机性数据,必要时用来补充。

7

/dev/random/dev/urandom的输出有一点微妙的区别。正如之前提到的,/dev/urandom不会阻塞,也就是说它不会让你等着。原因是它的输出来自一个伪随机数生成器,这个生成器是从/dev/random里的“真实”随机数中获取种子的。

/dev/urandom的输出几乎总是足够随机的——它是一个高质量的伪随机数生成器,使用了随机种子。如果你真的需要更好的随机数据源,可以考虑使用带有硬件随机数生成器的系统——比如我的上网本里就有一个VIA C7,它可以生成相当多的真正随机数据(我从/dev/random里能稳定得到99.9kb/s,从/dev/urandom里能得到545kb/s)。

顺便提一下,如果你在生成密码,可能会想看看pwgen——它可以帮你生成好读的密码 :).

撰写回答