Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

PEP 506 – 向标准库添加 Secrets 模块

作者:
Steven D’Aprano <steve at pearwood.info>
状态:
最终
类型:
标准跟踪
创建:
2015年9月19日
Python 版本:
3.6
历史记录:


目录

摘要

本 PEP 提出向 Python 标准库添加一个模块,用于生成令牌等常见的安全相关函数。

定义

本提案中使用的一些常见缩写

  • PRNG

    伪随机数生成器。一种确定性算法,用于生成具有某些理想统计特性的随机数。

  • CSPRNG

    密码学安全的伪随机数生成器。一种用于生成抗预测的随机数的算法。

  • MT

    梅森旋转算法。一种经过广泛研究的 PRNG,目前用作 random 模块的默认值。

基本原理

本提案的动机是人们担心 Python 的标准库使得开发人员很容易无意中犯下严重的安全性错误。OpenBSD 的创始人 Theo de Raadt 联系了 Guido van Rossum,并表达了一些关于使用 MT 生成密码、安全令牌、会话密钥等敏感信息的担忧 [1]

尽管 random 模块的文档明确指出默认值不适用于安全目的 [2],但人们强烈认为许多 Python 开发人员可能会错过、忽略或误解此警告。特别是

  • 开发人员可能没有阅读文档,因此没有看到警告;
  • 他们可能没有意识到他们对模块的具体使用存在安全隐患;或者
  • 没有意识到可能存在问题,他们从未提供最佳实践的网站复制了代码(或学习了技巧)。

在 Google 上搜索“python 如何生成密码”时,第一个 [3] 结果是一个使用 random 模块 [4] 中的默认函数的教程。尽管它并非旨在用于 Web 应用程序,但类似的技术很可能在该情况下使用。第二个结果是关于生成密码的 StackOverflow 问题 [5]。大多数给出的答案,包括被接受的答案,都使用了默认函数。当一位用户警告说默认值很容易被破解时,他们被告知“我认为你太担心了。” [6]

这强烈表明,在生成(例如)密码或安全令牌时,现有的 random 模块是一个诱人的陷阱。

可以在首次提出此想法的帖子 [7] 中找到额外的(更具哲学意义的)动机。

提议

替代方案侧重于 random 模块中的默认 PRNG,旨在提供“默认安全”的密码学强基元,开发人员可以在不考虑安全性的情况下构建这些基元。(请参阅下面的备选方案。)本提案提出了不同的方法

  • 标准库已经提供了密码学强基元,但许多用户不知道它们的存在或何时使用它们。
  • 与其要求对密码学不了解的用户编写安全的代码,不如让标准库包含一组针对最常见需求的现成的“电池”,例如生成安全令牌。此代码将直接满足需求(“如何生成密码重置令牌?”),并作为开发人员可以学习的良好实践的示例 [8]

为此,本 PEP 提出向标准库添加一个新模块,建议的名称为 secrets。此模块将包含一组针对具有安全隐患的常见活动的现成函数,以及一些更低级的基元。

建议将 secrets 作为处理任何应保密(密码、令牌等)内容的首选模块,同时 random 模块保持向后兼容。

API 和实现

本 PEP 为 secrets 模块提出以下函数

  • 用于生成适合在(例如)密码恢复、作为会话密钥等中使用的令牌的函数,采用以下格式
    • 作为字节,secrets.token_bytes
    • 作为文本,使用十六进制数字,secrets.token_hex
    • 作为文本,使用 URL 安全的 base-64 编码,secrets.token_urlsafe
  • 对系统 CSPRNG 的有限接口,直接使用 os.urandomrandom.SystemRandom。与 random 模块不同,它不需要提供用于播种、获取或设置状态或任何非均匀分布的方法。它应该提供以下内容
    • 一个从序列中选择项目的函数,secrets.choice
    • 一个用于生成给定数量的随机位和/或字节作为整数的函数,secrets.randbits
    • 一个用于返回 0 到给定上限的半开范围内的随机整数的函数,secrets.randbelow [9]
  • 一个用于比较文本或字节摘要以确保相等性同时抵御定时攻击的函数,secrets.compare_digest

共识似乎是,无需向 random 模块添加新的 CSPRNG 来支持这些用途,SystemRandom 将足够。

Alyssa (Nick) Coghlan [10] 和 Tim Peters [11] 提供了一些说明性实现以及极简的 API。这个想法也在“cryptography”模块的问题跟踪器 [12] 上进行了讨论。以下伪代码应作为实际实现的起点

from random import SystemRandom
from hmac import compare_digest

_sysrand = SystemRandom()

randbits = _sysrand.getrandbits
choice = _sysrand.choice

def randbelow(exclusive_upper_bound):
    return _sysrand._randbelow(exclusive_upper_bound)

DEFAULT_ENTROPY = 32  # bytes

def token_bytes(nbytes=None):
    if nbytes is None:
        nbytes = DEFAULT_ENTROPY
    return os.urandom(nbytes)

def token_hex(nbytes=None):
    return binascii.hexlify(token_bytes(nbytes)).decode('ascii')

def token_urlsafe(nbytes=None):
    tok = token_bytes(nbytes)
    return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')

secrets 模块本身将是纯 Python,其他 Python 实现可以轻松地对其进行不变地使用,或根据需要进行调整。可以在 BitBucket [13] 上找到一个实现。

默认参数

一个难题是“我的令牌应该有多少字节?”。我们可以通过为“token_*”函数提供默认的熵量来帮助解决此问题。如果 nbytes 参数为 None 或未给出,则将使用默认熵。此默认值应足够大,以预期对中等安全性的用途安全,但预计将来会发生变化,甚至可能在维护版本中发生变化 [14]

命名约定

一个问题是模块中使用的命名约定 [15],是使用类似 C 的命名约定(如“randrange”)还是更 Pythonic 的名称(如“random_range”)。

仅为私有 SystemRandom 实例(例如 randrange)绑定方法或围绕此类方法的简单包装的函数应保留熟悉的名称。那些是新事物(例如各种 token_* 函数)将使用更 Pythonic 的名称。

备选方案

一种替代方案是更改 random 模块 [16] 提供的默认 PRNG。这遭到了相当大的怀疑和彻底的反对

  • 人们担心 CSPRNG 可能比当前的 PRNG 慢(在 MT 的情况下,它已经相当慢了)。
  • 某些应用程序(例如科学模拟和重播游戏)需要能够将 PRNG 播种到已知状态,而 CSPRNG 则根据设计缺乏此功能。
  • random 模块的另一个主要用途是初学者编写的简单的“猜数字”游戏,许多人不愿对 random 模块进行任何可能使该操作变得更难的更改。
  • 虽然没有提议从 random 模块中删除 MT,但人们对必须选择加入非 CSPRNG 或任何向后不兼容的更改的想法抱有相当大的敌意。
  • 针对 MT 的已证明攻击通常针对 PHP 应用程序。据信,PHP 的 MT 版本比 Python 的版本更容易受到攻击,这是由于糟糕的播种技术 [17]。因此,如果没有针对 Python 应用程序的已证明攻击,许多人反对向后不兼容的更改。

Alyssa Coghlan 之前建议 使用全局可配置的 PRNG,该 PRNG 默认使用系统 CSPRNG,但后来已将其撤回,转而支持本提案。

与其他语言的比较

  • PHP

    PHP 包含一个函数 uniqid [18],默认情况下,它返回一个基于当前时间(以微秒为单位)的 13 个字符的字符串。翻译成 Python 语法,它具有以下签名

    def uniqid(prefix='', more_entropy=False)->str
    

    PHP 文档警告说此函数不适用于安全目的。尽管如此,各种成熟且知名的 PHP 应用程序出于此目的使用它(需要引用)。

    PHP 5.3 及更高版本还包含一个函数 openssl_random_pseudo_bytes [19]。翻译成 Python 语法,它大致具有以下签名

    def openssl_random_pseudo_bytes(length:int)->Tuple[str, bool]
    

    此函数返回给定长度的伪随机字节字符串,以及一个布尔标志,指示该字符串是否被认为是密码学安全的。PHP 手册建议,除了旧版或损坏的平台外,返回任何非 True 的值都应该很少见。

  • JavaScript

    根据一项相当粗略的搜索[20],JavaScript 中似乎没有任何用于生成强随机值的知名标准函数。Math.random 经常被使用,尽管它存在严重的弱点,使其不适合用于加密目的[21]。近年来,大多数浏览器都开始支持 window.crypto.getRandomValues [22]

    Node.js 提供了一个丰富的加密模块,crypto [23],其中大部分内容超出了本 PEP 的范围。它确实包含一个用于生成随机字节的单个函数,crypto.randomBytes

  • Ruby

    Ruby 标准库包含一个模块 SecureRandom [24],其中包含以下方法

    • base64 - 返回一个 Base64 编码的随机字符串。
    • hex - 返回一个随机的十六进制字符串。
    • random_bytes - 返回一个随机的字节字符串。
    • random_number - 根据参数,返回范围(0, n)内的随机整数,或 0.0 到 1.0 之间的随机浮点数。
    • urlsafe_base64 - 返回一个随机的 URL 安全 Base64 编码字符串。
    • uuid - 返回一个版本 4 的随机通用唯一标识符。

模块名称应该是什么?

曾有人提议添加一个“random.safe”子模块,引用 Python 之禅的“命名空间是一个非常棒的主意”的格言。然而,禅语的作者 Tim Peters 反对这个想法[25],并建议使用顶级模块。

在 python-ideas 邮件列表上的讨论中,到目前为止,“secrets”这个名称获得了一些认可,并且没有遇到强烈反对。

已经存在一个同名的第三方模块[26],但它似乎未被使用且已被废弃。

常见问题解答

  • 问:这是一个真正的问题吗?MT 肯定足够随机,以至于没有人能够预测其输出。

    答:安全专业人士普遍认为 MT 在安全环境中是不安全的。重建 MT 的内部状态并不困难[27] [28],因此可以预测所有过去和未来的值。已知有多种针对使用 MT 进行随机性的系统的实际攻击[29]

  • 问:针对 PHP 的攻击是一回事,但针对 Python 软件是否存在已知的攻击?

    答:是的。至少在 Zope 和 Plone 中存在漏洞。Hanno Schlichting 评论道[30]

    "In the context of Plone and Zope a practical attack was
    demonstrated, but I can't find any good non-broken links about
    this anymore.  IIRC Plone generated a random number and exposed
    this on each error page along the lines of 'Sorry, you encountered
    an error, your problem has been filed as <random number>, please
    include this when you contact us'.  This allowed anyone to do large
    numbers of requests to this page and get enough random values to
    reconstruct the MT state.  A couple of security related modules used
    random instead of system random (cookie session ids, password reset
    links, auth token), so the attacker could break all of those."
    

    Christian Heimes 在 2012 年向 Zope 安全团队报告了此问题[31],至少存在两个相关的 CVE 漏洞[32],并且在 Django 中至少有一个针对此问题的解决方法[33]

  • 问:这是否可以替代 SSL 等专业的加密软件?

    答:不是。这是一个“包含电池”的解决方案,而不是一个功能齐全的“核反应堆”。它的目的是缓解一些基本的安全错误,而不是解决所有安全相关问题。引用 Alyssa Coghlan 指她之前提出的建议[34]

    "...folks really are better off learning to use things like
    cryptography.io for security sensitive software, so this change
    is just about harm mitigation given that it's inevitable that a
    non-trivial proportion of the millions of current and future
    Python developers won't do that."
    
  • 问:密码生成器呢?

    答:普遍认为,密码生成器的要求过于多样化,不适合作为标准库的一部分[35]。在模块的初始版本中不会包含任何密码生成器,而是在文档中将其作为示例(类似于 itertools 模块中的示例)[36]

  • 问:secrets 将在 Linux 上使用 /dev/random(会阻塞)还是 /dev/urandom(不会阻塞)?其他平台呢?

    答:secrets 将基于 os.urandomrandom.SystemRandom,它们是操作系统最佳加密随机源的接口。在 Linux 上,它可能是 /dev/urandom [37],在 Windows 上它可能是 CryptGenRandom(),但请参阅文档和/或源代码以了解详细的实现细节。

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-0506.rst

上次修改时间:2023-10-11 12:05:51 GMT