PEP 504 – 默认使用系统 RNG
- 作者:
- Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建日期:
- 2015 年 9 月 15 日
- Python 版本:
- 3.6
- 发布历史:
- 2015 年 9 月 15 日
摘要
Python 目前默认使用 `random` 模块中的模块级 API 的确定性 Mersenne Twister 随机数生成器,要求用户知道,当他们执行“安全敏感”工作时,他们应该改用加密安全的 `os.urandom` 或 `random.SystemRandom` 接口,或者第三方库,如 `cryptography`。
不幸的是,这种方法导致开发人员在不意识到自己在进行安全敏感工作的情况下使用默认模块级 API,从而使他们的用户面临不必要的风险。
这不是一个急性问题,而是一个慢性问题,并且安全漏洞的引入和利用之间通常存在很长的延迟,这意味着开发人员很难从经验中自然学习。
为了最终普遍解决这个问题,本 PEP 提议 Python 在 Python 3.6 中默认切换到使用系统随机数生成器,并要求开发人员通过使用新的 `random.ensure_repeatable()` API,或通过显式创建自己的 `random.Random()` 实例来选择加入全进程的确定性随机数生成器过程。
为了最大限度地减少对现有代码的影响,需要确定性的模块级 API 将隐式切换到确定性 PRNG。
PEP 撤回
在讨论本 PEP 时,Steven D’Aprano 提出了一个更简单的替代方案,即提供一个标准化的 `secrets` 模块,该模块提供“一种显而易见的方式”来处理安全敏感任务,例如生成默认密码和其他令牌。
Steven 的提案达到了理想的效果,即使生成此类令牌的简单方法与生成它们的正确方法保持一致,而不会对现有的 `random` 模块 API 造成任何兼容性风险。因此,本 PEP 已撤回,转而进一步研究和完善 Steven 的提案,即 PEP 506。
提案
目前,在安全敏感应用程序中使用 `random` 模块中的模块级函数永远是不正确的。本 PEP 提议在 Python 3.6+ 中更改此声明,改为:如果在此进程中(直接或间接)调用了 `random.ensure_repeatable()`,则在 `random` 模块中使用模块级函数不适用于安全敏感应用程序。
为了实现这一点,`random` 中的模块级可调用项将不再是 `random.Random` 实例的绑定方法,而是会更改为委托给现有 `random._inst` 模块属性的相应方法的函数。
默认情况下,此属性将绑定到一个 `random.SystemRandom` 实例。
一个新的 `random.ensure_repeatable()` API 将重新绑定 `random._inst` 属性到一个 `system.Random` 实例,恢复与先前 Python 版本相同的模块级 API 行为(除了额外的间接层)。
def ensure_repeatable():
"""Switch to using random.Random() for the module level APIs
This switches the default RNG instance from the cryptographically
secure random.SystemRandom() to the deterministic random.Random(),
enabling the seed(), getstate() and setstate() operations. This means
a particular random scenario can be replayed later by providing the
same seed value or restoring a previously saved state.
NOTE: Libraries implementing security sensitive operations should
always explicitly use random.SystemRandom() or os.urandom in order to
correctly handle applications that call this function.
"""
if not isinstance(_inst, Random):
_inst = random.Random()
为了最大限度地减少对现有代码的影响,调用以下任何模块级函数都将隐式调用 `random.ensure_repeatable()`
random.seedrandom.getstaterandom.setstate
没有对 `random.Random` 或 `random.SystemRandom` 类 API 提出任何更改——显式实例化自己的随机数生成器的应用程序将不受此提案的任何影响。
关于隐式选择加入的警告
在 Python 3.6 中,隐式选择加入确定性 PRNG 将使用以下检查发出弃用警告
if not isinstance(_inst, Random):
warnings.warn(DeprecationWarning,
"Implicitly ensuring repeatability. "
"See help(random.ensure_repeatable) for details")
ensure_repeatable()
警告的具体措辞应在 Stack Overflow 上添加合适的答案,就像为调用 print 时缺少括号的自定义错误消息所做的那样 [10]。
在 Python 2.7 切换到仅安全修复模式后的第一个 Python 3 版本中,弃用警告将升级为 RuntimeWarning,以便默认可见。
本 PEP *不* 提议移除确保进程范围使用的默认 RNG 成为确定性 PRNG 的能力,该 PRNG 在给定特定种子的情况下会产生相同的输出系列。该功能广泛用于建模和模拟场景,并且要求直接或间接调用 `ensure_repeatable()` 是一个足够的增强,可以解决在 Web 应用程序中使用模块级随机 API 而未充分考虑确定性 PRNG 的潜在安全隐患。
性能影响
由于 `random.Random` 和 `random.SystemRandom` 之间存在很大的性能差异,移植到 Python 3.6 的应用程序将在以下情况下遇到显著的性能回归:
- 应用程序正在使用模块级随机 API
- 不需要加密质量的随机性
- 应用程序尚未通过调用 `random.seed`、`random.getstate` 或 `random.setstate` 来隐式选择回确定性 PRNG
- 应用程序尚未更新为显式调用 `random.ensure_repeatable`
这将在 Python 3.6 的“What’s New”指南的“Porting”部分进行说明,并建议在受影响应用程序的 `__main__` 模块中包含以下代码
if hasattr(random, "ensure_repeatable"):
random.ensure_repeatable()
需要加密质量随机性的应用程序应无论速度如何都应使用系统随机数生成器,因此在这些情况下,本 PEP 中提出的更改将修复先前潜在的安全缺陷。
文档更改
`random` 模块文档将更新,将 `seed`、`getstate` 和 `setstate` 接口的文档移到模块的后面,以及新 `ensure_repeatable` 函数的文档和相关的安全警告。
该模块文档的该部分还将讨论通过 `ensure_repeatable` 启用的确定性 PRNG(游戏、建模与模拟、软件测试)以及默认使用的系统 RNG(加密、安全令牌生成)的各自用例。此讨论还将推荐后者使用第三方安全库。
基本原理
在期限和预算压力下编写安全软件是一个难题。这反映在关于个人身份信息数据泄露的定期通知 [1] 中,以及在新系统(如联网的汽车 [2])连接到互联网时未能考虑安全因素。此外,互联网上随处可见的许多编程建议 [#search] 根本没有考虑到计算机安全的数学秘术。这些问题加剧了这样一个事实:防御者必须覆盖 *所有* 潜在的漏洞,因为一个错误就可能颠覆其他防御措施 [11]。
使得最后一个方面尤其困难的一个因素是,API 的不当使用会产生*静默*的安全故障——即,发现自己所做的事情不正确的方式是,有人审查你的代码说“这是一个潜在的安全问题”,或者你负责的系统因此类疏忽而受到损害(并且当你的系统受到损害时,你不仅仍然对此负责,而且你的入侵检测和审计机制足以让你在事后弄清楚损害是如何发生的)。
这种情况是导致“安全疲劳”的一个重要因素,在这种情况下,开发人员(通常是正确的 [9])会觉得安全工程师总是说“不要用简单的方式做,这会产生安全漏洞”。
作为世界上最受欢迎的语言之一的设计者 [8],我们可以通过在更多情况下将简单的方式变成正确的方式(或至少是“不错误”的方式)来帮助解决这个问题,这样开发人员和安全工程师就可以花更多时间来缓解真正有趣的威胁,而花更少的时间来应对默认的语言行为。
讨论
为什么选择“ensure_repeatable”而不是“ensure_deterministic”?
这是一个专业术语的含义与该词的典型含义相冲突的例子,尽管它们*技术上*是相同的。
从技术角度来看,“确定性 RNG”意味着,在了解算法和当前状态的情况下,你可以可靠地计算未来的任意状态。
问题在于,“确定性”本身并没有传达这些限定词,因此熟悉常规含义但又不熟悉技术含义的附加限定词的人,很可能会将其解释为“可预测”或“不随机”。
“确定性”作为传统 RNG 描述的第二个问题是,它并没有真正告诉你,你使用传统 RNG 可以做些什么,而使用系统 RNG 却不能做。
“ensure_repeatable”旨在解决这两个问题,因为它的常见含义准确地描述了偏好确定性 PRNG 而非系统 RNG 的主要原因:通过提供相同的种子值或恢复先前保存的 PRNG 状态来确保你可以重复相同的输出系列。
仅更改 Python 3.6+ 的默认值
其他一些近期安全更改,例如升级 `ssl` 模块的功能以及默认切换到正确验证 HTTPS 证书,已被认为足够关键,足以证明将更改向后移植到所有当前支持的 Python 版本是合理的。
在这种情况下,差异在于程度——比原计划提前几年推出此特定更改所带来的额外好处不足以证明在维护版本中进行如此侵入性的更改所需的额外工作或稳定性风险是合理的。
保留模块级函数
除了普遍的反向兼容性考虑因素外,Python 还被广泛用于教育目的,我们尤其不希望使依赖于当前 `random` 模块 API 可用性的广泛教育材料失效。因此,本提案确保大多数公共 API 不仅可以不经修改地继续使用,而且不会产生任何新的警告。
隐式选择加入确定性 RNG 时的警告
需要隐式选择加入确定性 PRNG,因为 Python 被广泛用于建模和模拟目的,在这些目的中这是正确的做法,而且在许多情况下,这些软件模型没有专门的维护团队负责确保它们在最新版本的 Python 上正常工作。
不幸的是,使用 `os.urandom` 的数据显式调用 `random.seed` 也是一个常见的错误,并且在网上流传的许多有缺陷的“如何在 Python 中生成安全令牌”的指南中都有出现。
使用第一个 DeprecationWarning,然后最终使用 RuntimeWarning,来反对隐式切换到确定性 PRNG,旨在引导那些需要加密安全 RNG 的未来用户远离调用 `random.seed()`,并将那些真正需要确定性生成器的用户引导至显式调用 `random.ensure_repeatable()`。
避免引入用户空间 CSPRNG
最初关于此提案在 python-ideas [#csprng] 上的讨论建议引入一个加密安全的伪随机数生成器并默认使用它,而不是默认使用相对较慢的系统随机数生成器。
这种方法的问 [7] 是,它在安全敏感的情况下引入了一个额外的故障点,对于随机数生成可能甚至不在关键性能路径上的应用程序而言。
需要加密质量随机性的应用程序应无论速度如何都应使用系统随机数生成器,因此在这些情况下。
确定性 PRNG “足够安全”吗?
一句话,“不”——这就是为什么模块文档中有警告说不要将其用于安全敏感目的。虽然我们目前没有了解关于 Python 随机数生成器的具体研究,但对 PHP 随机数生成器的研究 [3] 表明,可以利用该子系统的弱点来协助对流行的 PHP Web 应用程序中的密码恢复令牌进行实际攻击。
然而,安全软件开发的一个规则是“攻击只会越来越好,绝不会变差”,所以到 Python 3.6 发布时,我们可能确实会看到对 Python 确定性 PRNG 的实际攻击被公开记录。
Python 生态系统的安全疲劳
在过去的几年里,整个计算行业一直在共同努力,将我们都依赖的共享网络基础设施升级到一个“默认安全”的立场。作为网络服务开发(包括 OpenStack IaaS 平台)和 Linux 系统通用系统管理中使用最广泛的编程语言之一,Python 生态系统也承担了相当大的负担,这对于在其他上下文中不那么关心这些问题的 Pythonista 来说是可以理解的令人沮丧。
这一考虑因素是本提案相对于最初发布到 python-ideas [#draft] 的概念草案在向后兼容性改进方面取得实质性进展的主要驱动因素之一。
致谢
- Theo de Raadt,感谢他向 Guido van Rossum 提出建议,让我们认真考虑默认使用加密安全的随机数生成器。
- Serhiy Storchaka、Terry Reedy、Petr Viktorin 以及 python-ideas 线程中的其他人,他们提出了透明地切换到 `random.Random` 实现的方法,当任何仅对确定性 RNG 有意义的函数被调用时。
- Nathaniel Smith,感谢他提供了关于 PHP 随机数生成器在用于生成密码重置令牌时实际攻击的参考。
- Donald Stufft,感谢他与网络安全专家进行了进一步讨论,他们建议引入一个用户空间 CSPRNG 所带来的复杂性,相对于直接使用系统 RNG 而言,收益不足。
- Paul Moore,感谢他雄辩地陈述了 Python 生态系统中当前的安全疲劳程度。
参考资料
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0504.rst
最后修改: 2025-02-01 08:59:27 GMT