为什么 Math.random() 的设计不是为了加密安全?

信息安全 密码学 javascript 随机的
2021-08-31 22:19:58

JavaScriptMath.random()函数旨在返回单个 IEEE 浮点值n,使得 0 ≤ n < 1。众所周知(或至少应该)众所周知,输出不是密码安全的。大多数现代实现使用很容易被破坏的XorShift128+算法由于人们在需要更好的随机性时错误地使用它并不少见,为什么浏览器不将其替换为 CSPRNG?我知道Opera 就是这样做的*, 至少。我能想到的唯一理由是 XorShift128+ 比 CSPRNG 更快,但在现代(甚至不是那么现代)计算机上,使用 ChaCha8 或 AES-CTR 每秒输出数百兆字节将是微不足道的。这些通常足够快,以至于优化的实现可能仅受系统内存速度的限制。即使是未优化的 ChaCha20 实现在所有架构上都非常快,而 ChaCha8 的速度是其两倍以上。

我知道它不能被重新定义为 CSPRNG,因为该标准明确不保证适用于加密使用,但浏览器供应商自愿这样做似乎没有不利之处。它将减少大量 Web 应用程序中的错误的影响,而不会违反标准(它只要求输出是四舍五入的IEEE 754数字)、降低性能或破坏与 Web 应用程序的兼容性。


编辑:一些人指出,这可能会导致人们滥用此功能,即使标准规定您不能依赖它来实现加密安全。在我看来,有两个相反的因素决定了使用 CSPRNG 是否会带来净安全收益:

  1. 错误的安全感- 本来会使用为此目的而设计的功能的人数,例如window.crypto,决定使用Math.random()它,因为它恰好在他们的预期目标平台上是加密安全的。

  2. Opportunistic security - 不了解更多信息并Math.random()无论如何都使用敏感应用程序的人数,这些应用程序可以避免自己的错误。显然,最好对他们进行教育,但这并不总是可行的。

可以肯定的是,可以避免自己犯错的人数将大大超过陷入虚假安全感的人数。

* 正如 CodesInChaos 指出的那样,由于 Opera 基于 Chromium,这不再适用。


几个主要浏览器都有错误报告,建议用加密安全的替代方法替换此功能,但没有一个建议的安全更改落地:

改变的论点基本上与我的一致。反对它的论点从降低微基准测试的性能(在现实世界中几乎没有影响)到误解和神话,例如随着时间的推移 CSPRNG 会随着更多随机性的产生而变得更弱的错误想法。最后,Chromium 创建了一个全新的加密对象,Firefox 用 XorShift128+ 算法替换了他们的 RNG。Math.random()功能仍然是完全可预测的。

4个回答

在 1990 年代中后期,我是 JScript 的实施者和 ECMA 委员会的成员之一,因此我可以在这里提供一些历史视角。

JavaScript Math.random() 函数旨在返回一个介于 0 和 1 之间的浮点值。众所周知(或至少应该如此)输出不是加密安全的

首先:许多 RNG API 的设计很糟糕.NET Random 类可以很容易地以多种方式被滥用以产生相同数字的长序列,这一事实非常糟糕。一个使用它的自然方式也是错误方式的 API 是一个“失败的坑”API;我们希望我们的 API 成为成功的坑,自然的方式和正确的方式是相同的。

我认为可以公平地说,如果我们知道我们现在所知道的,那么 JS 随机 API 会有所不同。即使是简单的事情,比如将名称更改为“伪随机”也会有所帮助,因为正如您所注意到的,在某些情况下,实现细节很重要。在架构级别上,您有充分的理由希望random()成为一个返回表示随机或伪随机序列的对象的工厂,而不是简单地返回数字。等等。得到教训。

其次,让我们记住 1990 年代 JS 的基本设计目的是什么。 移动鼠标时让猴子跳舞我们认为内联表达式脚本很正常,我们认为 2 到 10 行脚本块很常见,有人可能在一页上写100 行脚本的想法真的很不寻常。我记得我第一次看到一个一万行的 JS 程序时,我对那些向我寻求帮助的人提出的第一个问题是,与他们的 C++ 版本相比,它是如此的慢,是某个版本的“你疯了吗?!10KLOC JS?! "

任何人都需要JS中的加密随机性的想法同样疯狂。你需要你的猴子动作是不可预测的加密强度吗?不太可能。

另外,请记住那是 1990 年代中期。如果您不在那里,我可以告诉您,就加密而言,这是一个与今天截然不同的世界……请参阅密码学的出口

如果没有从 MSLegal 团队获得大量法律建议,我什至不会考虑将加密强度随机性放入浏览器附带的任何内容中。在一个航运代码被认为是向国家敌人出口弹药的世界里,我不想用一根 10 英尺的杆子接触加密货币从今天的角度来看,这听起来很疯狂,但那是那个世界

为什么浏览器不将其替换为 CSPRNG?

浏览器作者不必提供不进行更改的理由。改变需要花钱,而且他们会从更好的改变中花费精力;每一次改变都有巨大的机会成本

相反,你必须提供一个论据,不仅是为什么做出改变是一个好主意,而且为什么它是最好的利用他们的时间。这是一个物超所值的改变。

我知道它不能被重新定义为 CSPRNG,因为标准明确不保证适用于加密使用,但无论如何这样做似乎没有缺点

不利的一面是,开发人员仍然无法可靠地知道他们的随机性是否是加密强度,并且更容易陷入依赖标准无法保证的属性的陷阱。提议的更改实际上并不能解决问题,这是一个设计问题。

因为实际上有一个加密安全的替代方案Math.random()

window.crypto.getRandomValues(typedArray)

这允许开发人员使用正确的工具来完成工作。如果您想为您的游戏生成漂亮的图片或战利品掉落,请使用快速Math.random(). 当您需要加密安全的随机数时,请使用更昂贵的window.crypto.

JavaScript (JS) 于 1995 年发明。

  1. 潜在非法:密码学在 1995 年仍处于严格的出口管制之下,因此一个好的 CSPRNG 甚至可能在浏览器中分发都不是合法的。
  2. 性能:从历史上看,CSPRNG(密码安全的伪随机数生成器)比 PRNG 慢得多,那么为什么默认使用 CSPRNG?
  3. 没有安全意识: 1995 年,SSL 刚刚被发明。几乎没有服务器支持它,互联网由电话线组成,用于公共论坛 ( BBS ) 和MUD加密是情报机构的事情。
  4. 不需要: Web 应用程序还不存在,因为 JavaScript 刚刚被发明出来。它被设计为一种解释性(因此很慢)语言,以帮助页面更加动态。默认情况下,任何人都不会想到将缓慢(几乎不存在)的 CSPRNG 用于随机函数。
  5. 事实上,几乎没有什么需要,以至于别无选择:直到 2013 年 12 月,JavaScript 甚至还没有一个普遍支持的用于 CSPRNG 的 API,所以直到几年前,Web 应用程序中的正确密码学几乎是不可能的。
  6. 一致性:他们没有将现有函数更改为具有不同的含义,而是创建了一个具有不同名称的新函数。您现在可以通过 访问 CSPRNG crypto.getRandomValues

总而言之:遗留,而且速度和一致性不安全的 PRNG 仍然更快,因为您不能假设所有硬件都支持 AES,也不能依赖 RDRAND 的可用性或安全性。

就个人而言,我认为是时候用 CSPRNG 替换所有随机函数,并将更快、不安全的函数重命名为fast_insecure_random(). 只有科学家或其他进行需要大量随机数的模拟的人才需要它们,但 RNG 的可预测性不是问题。但是对于一个有 20 年历史的功能,现在(2018 年)仅存在四年的替代方案,我可以理解为什么我们还没有到那个时候。

评论太长了。

我相信你的问题有一个有缺陷的前提:

在现代(甚至不是那么现代)的计算机上,使用 ChaCha8 或 AES-CTR 每秒输出数百兆字节是微不足道的

您正在考虑连接交流电的机器或带有大号 honkin 10Ah 电池的笔记本电脑上的桌面浏览器。

我们生活在一个越来越以移动为先的世界中,尽管如今的移动设备非常强大,但它们有两个重要的限制:热量和电池寿命。与可以轻松达到 100C 的台式机处理器不同,您不能烧毁智能手机用户的手。手机电池的容量通常只有笔记本电脑的1/3(如果幸运的话)。如果您不需要它,根本没有充分的理由增加额外的热量产生/功耗。