目录:
- 什么是随机数
- 随机数分类
- 伪随机数生成器
- 真随机数生成器
- 各种语言中的随机数
- 使用系统时间作为种子是否安全
什么是随机数
参考维基百科随机数
随机数的随机性检验可以分为三个标准:
- 统计学随机性。 统计学伪随机性指的是在给定的随机比特流样本中,1的数量大致等于0的数量,同理,“10”“01”“00”“11”四者数量大致相等。
- 密码学安全伪随机性(不可预测性 )。 其定义为,给定随机样本的一部分和随机算法,不能有效的演算出随机样本的剩余部分。
- 真随机性(不可重现性)。 其定义为随机样本不可重现。除非将数列本身保存下来,否则不能重现相同的数列。
随机数分类
类型 | 统计学随机性 | 不可预测性 | 不可重现性 | 备注 | 是否可用于密码技术 | 生成器 |
---|---|---|---|---|---|---|
伪随机数 | √ | Χ | Χ | 只具备随机性 | Χ | 伪随机数生成器 PRNG (Preudo Random Number Generator) |
密码学安全的伪随机数 | √ | √ | Χ | 具备不可预测性 | √ | 密码学伪随机数生成器 CPRNG (Cryptography secure Preudo Random Number Generator) |
真随机数 | √ | √ | √ | 具备不可重现性 | √ | 真随机数生成器 TRNG (True Random Number Generator) |
真随机数是否存在?
真随机数是否存在这是一个有争议的问题。
下面是维基百科的一段话:
实际上只要给定边界条件,真随机数并不存在,可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉(比如计算机当地的本底辐射波动值),可以认为用这个方法演算出来了真随机数。 但实际上,这也只是非常接近真随机数的伪随机数,一般认为,无论是本地辐射、物理噪音、抛硬币……等都是可被观察了解的,任何基于经典力学产生的随机数,都只是伪随机数。
但是本文这里把本地辐射、物理噪音、抛硬币这种,认为是真随机数,下面所说的真随机数都是指这些。
伪随机数生成器
伪随机数生成器是由外部输入的种子和内部状态两者生成的伪随机数列。
生成伪随机数有以下几种算法:
- 线性同余法(非密码学安全)
- 单向散列函数法(密码学安全)
- 密码法(密码学安全)
- ANSI X9.17(密码学安全)
1. 线性同余法
线性同余法就是将当前的伪随机数值乘以 A 再加上 C,然后将除以 M 得到的余数作为下一个伪随机数。
R0 = (A * 种子 + C) mod M
R1 = (A * R0 + C) mod M
R2 = (A * R1 + C) mod M
Rn = (A * R(n-1) + C) mod M
线性同余具有周期性,根据周期即可预测未来的状态。所以它不是密码学安全的伪随机数。
C 语言的库函数rand,以及Java的java.util.Random类等,都采用了线性同余法。因此这些函数都不能用于密码技术。
2. 单向散列函数法
单向散列函数也可以生成不可预测的伪随机数,且为密码学安全的伪随机数(因为它的单向性,具备不可预测性)。
3. 密码法
使用密码法也能生成密码学安全的伪随机数,既可以使用 AES 对称加密,也可以使用 RSA 公钥加密。
4. ANSI X9.17
用 ANSI X9.17 方法也可以生成密码学安全的伪随机数。
真随机数生成器
/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。
这两个设备的差异在于:
- /dev/random依赖于系统中断,系统的中断数不足时,/dev/random设备会一直封锁,尝试读取的进程就会进入等待状态,但 /dev/random设备的随机性很高。
- /dev/urandom 不依赖系统的中断,也就不会造成进程忙等待,但是随机性不高。
Windows操作系统的CryptGenRandom接口提供类似的功能。
各种语言中的随机数
java
- java.util.Random() / Math.random() / java.util.concurrent.ThreadLocalRandom ():使用线性同余方法,是非密码学安全的随机数。
- java.Security.SecureRandom:产生的是密码学安全的随机数。
SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); //通过hash算法产生密码学安全的伪随机数
同时也可以用NativePRNGBlocking或NativePRNGNonBlocking方法(NativePRNGBlocking使用/dev/random;NativePRNGNonBlocking使用/dev/urandom)
C/C++
- rand():线程不安全,线性同余算法->可以预测
- Random_device: c++11,不可预测,读取dev/urandom或调用RtlGenRandom作为随机源
使用系统时间作为种子是否安全
随机数的种子,决定了随机数的安全性。
如果只是使用系统时间作为随机数的种子,是不安全的:
- 同一时间启动的并发进程将会生产一样的随机数。
- 一天只有86400秒,即使爆破也很容易。
我们应该使用真随机数作为伪随机数的种子,例如java中,无参构造函数实例化SecureRandom,默认的算法是“nativePRNG”,就是从/dev/random获取随机数。