扒一扒随机数(Random Number)的诞生历史

2,437 阅读10分钟

作者:Alon Zakai

编译:胡子大哈

翻译原文:huziketang.com/blog/posts/…

英文原文:A Brief History of Random Numbers

转载请注明出处,保留原文链接以及作者信息


(罗马 12mm 骰子,大英博物馆便携式文物保护方案-CC BY-SA 2.0)

“在所有的产生随机数的事物中,我认为没有什么能够超越骰子了”,这是统计学家 Francis Galton 在 1890 年的《自然》杂志中写道。它们在容器中不断地翻滚、互相撞击,以各种形式和角度与容器壁发生碰撞,在容器中的位置和形态在外界看来都是那么不可预知,容器哪怕只发生一次晃动,外界都不可能知道里面到底是什么形态。

古已有之的随机数

到底如何才能生成均匀的随机数列呢?自然界中随机性大量而近乎完美的存在,人类并不能准确地预知和量化这种随机性。迄今为止发现最早的骰子(4 个面)是来自中东的一座公元前 24 世纪的坟墓里。再近一些的历史是在公元前 1100 年的中国,利用火烧龟壳产生的随机龟裂现象,一些“先知”会根据龟裂情况来对未来做判断。又过了几个世纪,在中国诞生了易经占卜法,利用 49 蓍草法进行占卜,其操作的分裂过程很类似于抛硬币。

机器生成随机数的第一次触碰

(摘自:“ A Million Random Digits with 100,000 Normal Deviates”)

时间到了 20 世纪 40 年代中期,现代世界需要更多的随机数,不再是骰子或者蓍草可以满足的了。RAND 公司发明了一种机器,通过随机脉冲发生器可以生成大量的随机数。他们将这个机器运行所产生的数字聚合起来并发布成图书“A Million Random Digits with 100,000 Normal Deviates”。这在现在看来是十分荒谬的,但是在当时却是一个突破。这是人类第一次产生如此大量的、高质量的随机数,并且对公众是开放的。这本书 RAND 公司一直印刷到了 2001 年,现在在亚马逊上也可以看得到

于此类似的机器:摇奖机,是由著名的 Bletchley Park WWII 破译小组在 20 世纪 40 年代发明的,当时被用来生成英国保险债券彩票所使用的随机数。为了平息公众对摇奖机的公平性和准确性的质疑和担心,官方斥资制作了当时的巨型纪录片:“摇奖机的重要性(The Importance of Being E.R.N.I.E.)”。下面给出视频,很值得一看。


(The Importance of Being E.R.N.I.E.)

1951 年随机性终于被正式规范化并且整合到了计算机 Ferranti Mark 1 号中。Ferranti Mark 1 号内置了随机数生成指令,利用电气噪声可以一次性生成 20 个随机比特位。这一特性是由阿兰·图灵设计的。Christopher Strachey 利用这一特点,编写了一套随机情书生成器。下面这是情书例子,利用这个程序生成的 David Link 的 2009 复合计划

JEWEL LOVE
MY LIKING HUNGERS FOR YOUR ADORABLE INFATUATION.
YOU ARE MY EROTIC ARDOUR.: MY FOND RAPTURE. MY THIRST
SIGHS FOR YOUR INFATUATION. MY HEART SEDUCTIVELY WISHES YOUR
BREATHLESS LONGING.

YOURS CURIOUSLY

M. U. C.

(由于上面文字过于漏骨,译者尝试引申出译文如下)

我对你的可爱迷恋至极。

你勾起了我所有对情爱的幻想。

我为你而狂热。

你的魅力使我对你充满了渴望。

我的心随你在而让我无法呼吸。

你的追求者

M.U.C

但是图灵的随机数指令几乎是当时的开发人员崩溃的,因为这种随机在本身就已经很不稳定的开发环境下又引入了不确定性。人们希望在软件中得到一致性的结果,但是用这种指令的软件永远不可能得到可重复的一致性结果,这也使得软件测试几乎变的不可行。

那么如果随机数生成器可以由一个确定性的函数来替代会怎样呢?如果在给定一个确定的初始条件,每次可以生成同样的随机序列会怎样呢?这就是伪随机数生成器(PRNG)。

伪随机数生成器(PRNG)

伪随机数生成器是由冯诺依曼在 1946 年创造的。他的基本思想是从一个随机数种子开始,对其平方,然后取中间值。接下来重复对得到的数取平方并取中间值的过程,就会得到一个具有统计意义属性的随机数序列了。这也就是广为人知的平方取中法

然而,冯诺依曼的方法并没有经得住时间的考验,因为不论从什么随机种子开始,序列最终都会落入某个短循环序列,比如:8100,6100,4100,8100,6100,4100……。

序列中的数字是依赖于前一个数字的这种生成函数,上面的重复循环问题是不可避免的。但是如果说这个循环间隔非常非常大,对实际应用并不会产生影响,那会怎样呢?

1949 年,数学家 D.H.Lehmer 利用线性同余生成器(LCG)实现了这一思路。下面给出的是基于 Lehmer 的方法所实现的一种朴素 PRNG,叫做中央随机数生成器,使用 JavaScript 在 1995 年写的。

    // The Central Randomizer 1.3 (C) 1997 by Paul Houle (paul@honeylocust.com)
    // See:  http://www.honeylocust.com/javascript/randomizer.html
    rnd.today=new Date();
    rnd.seed=rnd.today.getTime();
    function rnd() {
      rnd.seed = (rnd.seed*9301+49297) % 233280;
      return rnd.seed/(233280.0);
    };

    function rand(number) {
      return Math.ceil(rnd()*number);
    };

注意代码中的魔法数字(如 9301 等),这些数字(通常是质数)是用来最大化重复区间的——上面所提到的自我重复的循环区间。这种 PRNG 使用当前时间作为种子值,重复区间可以达到 2 的 31 次方。

这种中央随机生成器发明之初非常流行,因为那时的 JavaScript 1.0 还没有内置 Math.random() 函数,当时的 Web 1.0 环境下,大家都想让自己的 banner 广告随机旋转。一个开发者 Paul Houle 说道:“它在很多情况下已经很好用了,但是不能使用它来做保密使用”。

对 PRNG 的更高要求

互联网确实需要保密。SSL 诞生在 1995 年,它的加密方案需要高质量的 PRNG。它的发展也直接导致了一段时间的 PRNG 野蛮创新时期。如果你回头看一下所有的随机数生成器的专利,你可能会感受到就像现代版的第一次制造飞机的浪潮一样。

20 世纪 90 年代中期的 CPU 是没有内置随机数生成指令的,这使得那时候好的随机种子特别难得。本来这问题也不大,不过当飞利浦的 Hallam-Baker 发现 Netscape(当时市场上的巨头)的 SSL web 服务器使用了“当前时间 + 一组特殊 ID”组合作为种子的时候,这个问题变成了一个切身体会到的安全问题了。Hallam-Baker 展示了一个攻击者很容易猜到种子值,并且对他们所拿到的服务器流量进行解密的过程。猜种子值是一个非常常规的攻击手段,尽管这种手法现在变得越来越困难。这里给出 2009 年在 Hacker News 上的一段非常经典的攻击演练

到了 1997 年,计算机科学家们厌倦了生成随机数所受限的条件,来自 SGI 的一个团队发明了 LavaRand,它是用一个网络摄像头来对着熔岩灯拍照。从摄像头中过来的图片数据是一个真实的熵源——像图灵那样的真实随机数生成器(TRNG)——可以以 165kb/s 的速率生成随机数。一如当时硅谷的风格,熔岩灯平台很快拿到了专利

AutoDesk 的创始人 John Walker 在全世界范围内推广他的 HotBits,这是一种“随机数即服务”的应用,背后原理是盖革计数器来保证其量子随机性。1998 年成立的 Random.org 为互联网提供真正的随机数。他们提供的服务包括真正的抛硬币随机、骰子随机和卡牌洗牌随机等。

上面所提到的大多数算法后来都无人问津了,但是一个叫做梅森旋转随机数生成器(The Mersenne Twister)的软件 PRNG 鹤立鸡群,它是由松本真(Makoto Matsumoto)和西村 拓士(Takuji Nishimura)在 1997 年发明的。它完美地平衡了性能和随机数的质量,并且经受住了时间的考验。其基本思想是基于线性反馈移位寄存器(LFSR),产生一个循环周期非常长的确定性序列,循环周期能够达到 2¹⁹⁹³⁷− 1。在当前的编程语言中,这种算法依旧是默认的 PRNG。

在 1999 年,随机数市场发生了一个巨大的变化,Intel 在其 i810 芯片组上集成了芯片级的随机数生成器。这样使得新的服务器都自带热噪声的本地源随机数生成能力——真正的随机数生成器(TRNG)。这很伟大,但是它始终没有软件 PRNG 快,所以加密软件依旧不得不依赖于伪随机数生成器(PRNG)。

这就把我们带到了“密码安全 PRNG”(CSPRNG)(这些讨厌的缩写!难怪很多人认为计算机科学很烦人)。CSPRNG 对于 SSL 特别重要。那么 CSPRNG 的原理是什么呢?这里有一份 131 页的论文来介绍 CSPRNG。祝你在里面阅读愉快。

不言而喻,CSPRNG 是一个强需求。梅森旋转随机数生成器并不是一种 CSPRNG,因为如果可以给定大量的先前序列样本,后面的数字是可以预计的出来。

时间再拉近一些,2012 年,Intel 为 TRNG 增加了 RDRANDRDSEED 指令,具有 500MB/s 的生产效率。但是 RDRAND 的完整性一直被质疑,里面是不是有某些缺陷?或者是为美国国家安全局内置了什么东西?没人确切地知道这个问题的答案,我猜某些地方的某些人一定知道,可是他们也一定不会公开。

开源硬件随机数生成器


(由一种硬件随机数生成器 PEDOUBLER 生成的随机数据)

近些年开源硬件 TRNG 也逐渐显露头角。它们广受欢迎得益于其设计的透明化:你可以自己构建线路,也可以用现有的组件搭建。完全的透明化使得对硬件随机数生成没有任何的担心和疑虑。REDOUBLER无限噪声 TRNG是两个开源硬件随机数生成器,链接中给出他们的 Github 源码地址。

结尾

今天,依旧有关于对随机数生成方法选择的争论,在操作系统内核、编程语言和安全包(如 OpenSSL 或者 OpenSSH)方面均未停止。有许多不同的算法聚焦于不同的特点上,如速度、占用空间、安全性等方面,也有一些安全专家依旧在寻找攻破已有算法的方法。但是对于我们日常的使用来讲,在大多数的操作系统中你可以放心地使用 /dev/random,或者编程语言中你可以随心地使用 rand() 函数,都能给你带来很好的使用体验,并且你这么做,阿兰·图灵也会很开心。

欢迎大家关注我的前端大哈 - 知乎专栏,定期发布高质量前端文章。


我最近正在写一本《React.js 小书》,对 React.js 感兴趣的童鞋,欢迎指点