PEP 504 – 默认使用系统 RNG
- 作者:
- Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 已撤回
- 类型:
- 标准化轨迹
- 创建:
- 2015-09-15
- Python 版本:
- 3.6
- 发布历史:
- 2015-09-15
摘要
Python 当前默认情况下为 random
模块中的模块级 API 使用确定性 Mersenne Twister 随机数生成器,要求用户在执行“安全敏感”工作时切换到使用加密安全的 os.urandom
或 random.SystemRandom
接口,或者使用像 cryptography
这样的第三方库。
不幸的是,这种方法导致了这样的情况:开发者没有意识到他们正在进行安全敏感工作,使用了默认的模块级 API,从而将他们的用户置于不必要的风险之中。
这不是一个尖锐的问题,但它是一个慢性问题,并且安全漏洞的引入和利用之间往往存在很长的延迟,这使得开发者难以从经验中自然学习。
为了为这个问题提供一个最终普遍的解决方案,本 PEP 提案建议在 Python 3.6 中,Python 默认情况下使用系统随机数生成器,并要求开发者通过使用新的 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.seed
random.getstate
random.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 在整个进程中使用的能力,该 RNG 是一个确定性 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 指南的移植部分中注明,建议在受影响应用程序的 __main__
模块中包含以下代码
if hasattr(random, "ensure_repeatable"):
random.ensure_repeatable()
确实需要加密质量随机性的应用程序应该使用系统随机数生成器,而无需考虑速度因素,因此,在这种情况下,本 PEP 中提出的更改将修复以前潜在的安全缺陷。
文档变更
random
模块文档将更新,将 seed
、getstate
和 setstate
接口的文档移到模块的后面,以及新 ensure_repeatable
函数和相关安全警告的文档。
模块文档的该部分还将讨论由 ensure_repeatable
(游戏、建模和模拟、软件测试)启用的确定性 PRNG 和默认使用的系统 RNG(加密、安全令牌生成)的各自用例。本讨论还将建议在后一项任务中使用第三方安全库。
原理
在截止日期和预算压力下编写安全软件是一个难题。这反映在定期通知涉及个人身份信息的数据泄露事件 [1],以及在将新系统(如机动车 [2])连接到互联网时未考虑安全因素。同样,网上 readily available 的许多编程建议 simply 没有考虑到计算机安全的数学奥秘。加剧这些问题的是,防御者必须覆盖他们所有 potential 的漏洞,因为一个错误可能使 subversion 其他防御成为可能 [11]。
导致最后一个方面特别困难的因素之一是 API,其中不恰当地使用它们会造成隐性安全故障 - 唯一能够发现你所做的事情不正确的方法是让 someone 审查你的代码,说“这 potential 是一个安全问题”,或者是你负责的系统通过这种疏忽被破坏(不仅在系统被破坏时你仍然负责,而且你的入侵检测和审计机制足够好,让你能够在事件发生后弄清楚破坏是如何发生的)。
这种情况是“安全疲劳”的主要贡献者之一,在这种情况中,开发者(通常是 rightly [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 上继续工作。
不幸的是,明确调用 random.seed
并使用 os.urandom
中的数据也是一个错误,这个错误出现在许多有缺陷的“如何在 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 基础设施即服务平台)和 Linux 系统上的一般系统管理的最广泛使用的编程语言之一,这方面的很大一部分负担落在了 Python 生态系统上,这对于在这些问题不那么令人担忧的其他环境中使用 Python 的 Pythonistas 来说是令人沮丧的。
该考虑因素是推动本提案相对于最初发布到 python-ideas [6] 的初始草案概念的重大向后兼容性改进的主要因素之一。
致谢
- Theo de Raadt,建议 Guido van Rossum 认真考虑默认使用加密安全的随机数生成器
- Serhiy Storchaka、Terry Reedy、Petr Viktorin 以及在 python-ideas 主题中建议在调用任何仅对确定性 RNG 有意义的功能时透明地切换到
random.Random
实现的其他人 - Nathaniel Smith 为提供关于在用于生成密码重置令牌时针对 PHP 随机数生成器的实际攻击的参考
- Donald Stufft 继续与网络安全专家进行的进一步讨论,这些讨论表明引入用户空间 CSPRNG 相对于直接使用系统 RNG 而言,意味着额外的复杂性,而收益却不足
- Paul Moore eloquently made the case for the current level of security fatigue in the Python ecosystem
参考资料
版权
本文件已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0504.rst
最后修改时间:2023-10-11 12:05:51 GMT