随机数,顾名思义就是随机出现的数,其在密码学、统计学及其它方面都有很广阔的应用。在前端领域,我们也经常使用随机数来防止缓存。那么随机数是如何生成的,另外经常听到的伪随机,这些是什么意思,今天我们就来学习一下。
在密码学中,随机数的随机性检验分为三个标准:
真随机数一般很难获得,在设计密码算法时,通常使用的随机数列,都是伪随机数列。另外如果一个随机数算法生成数列的周期足够大,可以通过各种随机性检验,也认为这种数是伪随机数。
下面就介绍两种伪随机数生成算法
线性同余生成器是最广泛使用的伪随机数生成器。它的算法非常简单:
X n + 1 = ( a X n + c ) m o d m X_{n+1} = (aX_n + c) \bmod m Xn+1=(aXn+c)modm
其中 m 为模数,m > 0;
a 为乘数,0 ≤ a < m;
c 为增量,0 ≤ c < m;
X 0 X_0 X0 为种子或初值,0 ≤ X 0 X_0 X0 < m
在这个算法中,a, c, m 的取值是保证产生高质量随机数的关键。
例如:a=c=1,则每次产生数字都是前一个数字加1,显然这个数列就不能算是伪随机。
所以为了保证随机数列的周期足够大,那么要求 m 的值就要足够大。一般 m 会取计算机存储的最大整数,比如32位机中,就取 2^31 - 1;另外,为了保证产生的数列是具有统计学上的随机性,需要产生的数是整周期的,对于 m 为素数,c=0,的情况,如果 a 是 m 的本原根,则产生的数列是整周期的。
对于 m=2^31 - 1的情况,a=7^5=16807是m的一个本原根,所以这样的随机数生成器是被广泛应用的。
X n + 1 = ( 7 5 X n ) m o d ( 2 31 − 1 ) X_{n+1} = (7^5X_n)\bmod(2^{31}-1) Xn+1=(75Xn)mod(231−1)
我们用 JavaScript 来实现一下此算法
let random = (function(x){
let a = 7 ** 5;
let c = 2 ** 31 - 1;
return function(){
x = (a * x) % c;
return x / c;
}
})(1)
这样我们就实现了一个产生 0-1 之间随机数的生成器。
运行效果如下:
for(let i = 0; i < 10; i++) {
console.log(random());
}
// 0.5373039755678288
// 0.46791736849952364
// 0.28721237149425427
// 0.17832770393152148
// 0.1537199770816229
// 0.5716548108363779
// 0.80240572700389
// 0.033053754378600864
// 0.5344498411447042
// 0.4984801190432534
我们看到,线性同余算法一旦初始值确定了之后,算法每次输出都是固定的,所以很容易被其他人破解。所以一般会有一些改进的方法来防止破解,例如每产生 n 个数之后,就利用当前时钟值 mod m 做为新的种子,或者每次计算时,都加上当前时钟值。这样就可以保证相对的安全。
BBS (blum-blum-shub)生成器,是由Lenore Blum, Manuel Blum 和 Michael Shub 在1986年提出的。其算法如下:
X n + 1 = X n 2 m o d M X_{{n+1}}=X_{n}^{2}{\bmod M} Xn+1=Xn2modM
B n = X n m o d 2 B_n = X_n \bmod 2 Bn=Xnmod2
其中 M=pq,p和q是两个大质数。 X 0 X_0 X0取值应与M互质。用 X n X_n Xn mod 2,可以得到一个比特序列{ B i B_i Bi}。
用 JavaScript 实现此算法如下:
let bbs = (function(x0, p, q){
let x = x0;
let m = p * q;
return function(){
x = x ** 2 % m;
return x % 2;
}
})(101355, 383, 503) // 我们设置x0=101355, p=383, q=503
我们看一下前20项的输出:
new Array(20).fill(1).map(v => bbs())
// [1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1]
我们应用这个伪随机比特生成器也很容易产生一个 0 - 1 的随机数,比如我们产生一个31位比特,再除以 2^31 - 1,就得到一个 0 - 1 的随机数。JavaScript 代码如下:
function random() {
return new Array(31).fill(1).map(v => bbs())
.reduce((acc, cur) => {
return acc * 2 + cur;
}) / (2 ** 31 - 1)
}
for(let i = 0; i < 10; i++) {
console.log(random());
}
// 0.703357007682024
// 0.6128253776639818
// 0.7413666051539437
// 0.9332527741479002
// 0.8812826480163646
// 0.8821496208580908
// 0.29817110313948764
// 0.5660664832992789
// 0.2635656507981781
// 0.02872776241448138
BBS 已经被证明过是安全的伪随机数产生器,它的安全性是基于大整数分解的困难性。
密码学上,安全的伪随机比特产生器定义为:以前k个比特作为输入,如果不存在多项式时间的算法,能以大于1/2的概率预测第k+1个比特,就认为这个伪随机比特产生器是安全的。
了解随机数生成算法,对理解其它密码算法是很有帮助的。这篇文章就介绍了随机数的线性同余算法和BBS算法,还有更多更高级的随机数生成算法,值得我们去探索。