密码学中的随机数

随机数,顾名思义就是随机出现的数,其在密码学、统计学及其它方面都有很广阔的应用。在前端领域,我们也经常使用随机数来防止缓存。那么随机数是如何生成的,另外经常听到的伪随机,这些是什么意思,今天我们就来学习一下。

随机数的特点

在密码学中,随机数的随机性检验分为三个标准:

  1. 统计学伪随机性
    即在给定的数列中,每个数字出现的数量大致相等。就如同掷骰子一样,掷的次数足够多,1 - 6 每个数字出现的次数基本是相等的。
  2. 密码学安全伪随机
    定义为,给定随机样本的一部分和随机算法,不能有效地演算出随机样本的剩余部分。
  3. 真随机性
    定义随机样本不可重现。为了产生真随机性,需要借助物理现象获取信息,一般认为如环境温度、声音、辐射变化等,这些是随机的,根据这些物理信息生成的数列,一般认为不可重现。

真随机数一般很难获得,在设计密码算法时,通常使用的随机数列,都是伪随机数列。另外如果一个随机数算法生成数列的周期足够大,可以通过各种随机性检验,也认为这种数是伪随机数。

下面就介绍两种伪随机数生成算法

线性同余生成器

线性同余生成器是最广泛使用的伪随机数生成器。它的算法非常简单:

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(2311)

我们用 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生成器

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算法,还有更多更高级的随机数生成算法,值得我们去探索。

你可能感兴趣的:(crypto)