linux - 关于Python3.6 os.urandom/os.getrandom/secrets的问题

标签 linux python-3.6

请引用ossecrets的文档:

os.getrandom(大小,标志= 0)

Get up to size random bytes. The function can return less bytes than requested.

getrandom() relies on entropy gathered from device drivers and other sources of environmental noise.



那么这是否意味着它来自/dev/random

On Linux, if the getrandom() syscall is available, it is used in blocking mode: block until the system urandom entropy pool is initialized (128 bits of entropy are collected by the kernel).



因此,要确保内部状态不好的内核CSPRNG是而不是,我应该使用os.getrandom()吗?由于该函数返回的字节数少于请求的字节数,因此应将应用程序级别的CSPRNG作为以下命令运行
def rng():
    r = bytearray()
    while len(r) < 32:
        r += os.getrandom(1)
    return bytes(r)

确保最大的安全性?我明确希望所有在初始化urandom熵池之前无法支持阻塞的系统都无法运行,并且无法运行支持该程序的系统。这是因为即使从启动时熵为零的实时CD运行,该软件也必须安全。

还是阻塞意味着如果我执行os.getrandom(32),程序将在必要时一直等待直到收集到32个字节为止?

The flags argument is a bit mask that can contain zero or more of the following values ORed together: os.GRND_RANDOM and GRND_NONBLOCK.



有人可以请ELI5如何运作吗?

os.urandom(大小)

On Linux, if the getrandom() syscall is available, it is used in blocking mode: block until the system urandom entropy pool is initialized (128 bits of entropy are collected by the kernel).



因此,urandom悄无声息地退回到了不阻塞的CSPRNG上,后者不知道它在较旧的Linux内核版本中是内部种子状态吗?

Changed in version 3.6.0: On Linux, getrandom() is now used in blocking mode to increase the security.



这和os.getrandom()有关吗?是较低级别的通话吗?两者一样吗?

os.GRND_NONBLOCK

By default, when reading from /dev/random, getrandom() blocks if no random bytes are available, and when reading from /dev/urandom, it blocks if the entropy pool has not yet been initialized.



所以它是os.getrandom(size,flag = 0)中的0标志吗?

os.GRND_RANDOM

If this bit is set, then random bytes are drawn from the /dev/random pool instead of the /dev/urandom pool.



ORing os.getrandom()标志是什么意思? os.getrandom(flags = 1)如何说明我是否打算启用os.GRND_NONBLOCK或os.GRND_RANDOM。还是我需要像这样设置它:
os.GRND_RANDOM = 1
os.getrandom(32) # or use the rng() defined above

secret 模块

The secrets module is used for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets.



唯一的生成随机字节的方法是
secrets.token_bytes(32)

The secrets module provides access to the most secure source of randomness that your operating system provides.



所以这应该意味着它是带有os.getrandom后备的os.urandom?因此,如果您希望“如果无法评估内部状态而优雅退出”,这不是一个好选择吗?

To be secure against brute-force attacks, tokens need to have sufficient randomness. Unfortunately, what is considered sufficient will necessarily increase as computers get more powerful and able to make more guesses in a shorter period. As of 2015, it is believed that 32 bytes (256 bits) of randomness is sufficient for the typical use-case expected for the secrets module.



但是,阻塞在内部状态的128位而不是256位处停止。出于某种原因,大多数对称密码具有256位版本。

因此,我应该确保在阻止模式下使用/dev/random,以确保在生成密钥时内部状态已达到256位?

所以tl; dr

在Python3.6(3.17或更高版本)的实时发行版中,在程序执行开始时内核CSPRNG内部状态的熵为零的Python3.6中生成256位密钥的最安全方法是什么?

最佳答案

经过研究后,我可以回答我自己的问题。os.getrandom是Linux内核3.17及更高版本中提供的getrandom() syscall的包装。该标志是一个数字(0、1、2或3),它通过以下方式对应于位掩码:
使用ChaCha20 DRNG获得GETRANDOM
os.getrandom(32,标志= 0)

GRND_NONBLOCK =  0  (=Block until the ChaCha20 DRNG seed level reaches 256 bits)
GRND_RANDOM   = 0   (=Use ChaCha20 DRNG)
              = 00  (=flag 0)
当不需要与Python 3.5和3.17之前的内核向后兼容时,这是在所有平台(包括现场发行版)上与所有Python 3.6程序一起使用的一个很好的默认值。
PEP 524 claims不正确

On Linux, getrandom(0) blocks until the kernel initialized urandom with 128 bits of entropy.


根据page 84 of the BSI report的说明,在引导期间,内核模块的get_random_bytes()函数的调用者使用128位限制,如果是,则该代码是为了正确等待add_random_ready_callback()函数的触发而进行的。 (不等待就意味着get_random_bytes()可能返回不安全的随机数。)根据第112页

When reaching the state of being fully seeded and thus having the ChaCha20 DRNG seeded with 256 bits of entropy -- the getrandom system call unblocks and generates random numbers.


因此,在ChaCha20 DRNG完全种子化之前,GETRANDOM()永远不会返回随机数。
os.getrandom(32,标志= 1)
GRND_NONBLOCK =  1  (=If the ChaCha20 DRNG is not fully seeded, raise BlockingIOError instead of blocking)
GRND_RANDOM   = 0   (=Use ChaCha20 DRNG)
              = 01  (=flag 1)
如果应用程序在等待ChaCha20 DRNG完全播种时需要执行其他任务,则该功能很有用。 ChaCha20 DRNG几乎总是在引导期间完全播种,因此flags=0最有可能是更好的选择。需要围绕它的try-except逻辑。
GETRANDOM与blocking_pool
也可以通过blocking_pool设备文件访问/dev/random。设计池时要考虑到熵耗尽的想法。此想法仅在尝试创建一次性填充(争取信息理论安全性)时适用。为此目的,blocking_pool中的熵质量尚不清楚,并且性能确实很差。对于其他用途,正确播种的DRNG足够了。blocking_pool可能更安全的唯一情况是使用4.17之前的内核,这些内核在编译时设置了CONFIG_RANDOM_TRUST_CPU标志,并且CPU HWRNG碰巧有后门。由于在那种情况下,ChaCha20 DRNG最初是使用RDSEED/RDRAND指令进行播种的,因此不良的CPU HWRNG将是一个问题。但是,根据BSI报告的第134页:

[As of kernel version 4.17] The Linux-RNG now considers the ChaCha20 DRNG fully seeded after it received 128 bit of entropy from the noise sources. Previously it was sufficient that it received at least 256 interrupts.


因此,直到还从input_pool混合了熵之后,才将ChaCha20 DRNG视为完全播种,该熵将来自所有LRNG噪声源的随机事件合并在一起。
通过将os.getrandom()23标志一起使用,熵来自blocking_pool,后者从input_pool接收熵,继而又从几个其他噪声源接收熵。 ChaCha20 DRNG也从input_pool重新播种,因此CPU RNG对DRNG状态没有永久控制权。一旦发生这种情况,ChaCha20 DRNG和blocking_pool一样安全。
os.getrandom(32,标志= 2)
GRND_NONBLOCK =  0  (=Return 32 bytes or less if entropy counter of blocking_pool is low. Block if no entropy is available.)
GRND_RANDOM   = 1   (=Use blocking_pool)
              = 10  (=flag 2)
这需要一个运行该函数的外部循环,并将返回的字节存储到缓冲区中,直到缓冲区大小为32个字节为止。这里的主要问题是由于blocking_pool的阻塞行为,获取所需的字节可能需要很长时间,尤其是在其他程序也从同一syscall或/dev/random请求随机数的情况下。另一个问题是,与使用os.getrandom(32, flags=2)标志相比,使用3的循环花费更多的空闲时间等待随机字节(请参见下文)。
os.getrandom(32,标志= 3)
GRND_NONBLOCK =  1  (=return 32 bytes or less if entropy counter of blocking_pool is low. If no entropy is available, raise BlockingIOError instead of blocking).
GRND_RANDOM   = 1   (=use blocking_pool)
              = 11  (=flag 3)
如果应用程序在等待blocking_pool具有一定量的熵时需要执行其他任务,则该功能很有用。需要围绕它的try-except逻辑以及一个运行该函数并将返回的字节存储到缓冲区中直到缓冲区大小为32字节的外部循环。
其他
打开('/dev/urandom','rb')。read(32)
为了确保向后兼容,与使用ChaCha20 DRNG的GETRANDOM()不同,从/dev/urandom设备文件读取不会阻塞。无法保证随机数的质量,这是不好的。这是最不推荐的选项。
os.urandom(32)os.urandom(n)提供尽力而为的安全性:
Python3.6
在Linux 3.17及更高版本上,os.urandom(32)os.getrandom(32, flags=0)等效。在较旧的内核上,它安静地回落到open('/dev/urandom', 'rb').read(32)的等效值,这不好。
首选os.getrandom(32, flags=0),因为它不能退回到不安全模式。
Python3.5及更早版本
总是等效于open('/dev/urandom', 'rb').read(32),这不好。由于os.getrandom()不可用,因此不应使用Python3.5。
secrets.token_bytes(32)(仅适用于Python 3.6)os.urandom()的包装器。密钥的默认长度为32字节(256位)。在Linux 3.17及更高版本上,secrets.token_bytes(32)os.getrandom(32, flags=0)等效。在较旧的内核上,它安静地回落到open('/dev/urandom', 'rb').read(32)的等效值,这不好。
同样,应首选os.getrandom(32, flags=0),因为它不能退回到不安全模式。
tl; dr
使用os.getrandom(32, flags=0)
那么其他RNG源,random,SystemRandom()等呢?
import random
random.<anything>()
对于创建密码,加密密钥等,绝对不安全。
import random
sys_rand = random.SystemRandom()
可以安全地用于和密码!
sys_rand.sample()
使用sys_rand.sample(list_of_password_chars, counts=password_length) 生成随 secret 码不是安全的,因为用documentation引用,sample()方法用于“随机采样而无需替换”。这意味着密码中的每个后续字符都保证不包含任何先前的字符。这将导致密码为而不是统一随机的
sys_rand.choices()sample()方法用于随机采样,而无需替换choices()方法用于替换
的随机采样。但是,在choices上引用documentation

The algorithm used by choices() uses floating point arithmetic for internal consistency and speed. The algorithm used by choice() defaults to integer arithmetic with repeated selections to avoid small biases from round-off error.


因此,使用的浮点算术choices()方法向采样的密码引入了密码学上不可忽略的偏差。因此, random.choices()不得用于密码/密钥生成!
sys_random.choice()
根据先前引用的文档,sys_random.choice()方法使用整数算法而不是浮点算法,因此使用重复调用sys_random.choice()生成密码/密钥是安全的。
secrets.choice()secrets.choice()sys_random.choice()的包装,并且可以与random.SystemRandom().choice()互换使用:它们是同一回事。
recipe for best practice to generate a passphrase with secrets.choice()
import secrets
# On standard Linux systems, use a convenient dictionary file.
# Other platforms may need to provide their own word-list.
with open('/usr/share/dict/words') as f:
    words = [word.strip() for word in f]
    passphrase = ' '.join(secrets.choice(words) for i in range(4))
如何确保生成的密码短语符合某些安全级别,例如128位?
这是一个食谱
import math
import secrets

def generate_passphrase() -> str:
    
    PASSWORD_MIN_BIT_STRENGTH = 128  # Set desired minimum bit strength here

    with open('/usr/share/dict/words') as f:
        wordlist = [word.strip() for word in f]

    word_space = len(wordlist)
    word_count = math.ceil(math.log(2 ** PASSWORD_MIN_BIT_STRENGTH, word_space))

    passphrase = ' '.join(secrets.choice(wordlist) for _ in range(word_count))

    # pwd_bit_strength = math.floor(math.log2(word_space ** word_count))
    # print(f"Generated {pwd_bit_strength}-bit passphrase.")

    return passphrase

关于linux - 关于Python3.6 os.urandom/os.getrandom/secrets的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42190663/

相关文章:

python - 在 CPython 3.6 中获取字典中的第一个和第二个值

python - 如何在 Python 3.6 中验证 AWS Cognito 生成的 JWT 的签名?

python - 如何在没有 sys.path 的情况下从父目录导入模块?

linux - Linux中是否存在虚拟内存地址的复用?

c++ - 文件的 O_DIRECT 标志

python - MySQL-python 安装无法在 virtualenv 中运行

python - 在 Python 3.6 中运行时根据 Union 类型检查变量

regex - 同时获取一行的第一个元素和特定单词之后的另一个元素

linux - 如何读取/解析文件 syscall_32.tbl 以及谁在构建 Linux 内核时执行此操作?

linux - 系统延迟启动日历事件或等待其他服务的启动时间