随机数生成器与线性同余法产生随机数

1、随机数生成器与/dev/random:

随机数生成器,顾名思义就是能随机产生数字,不能根据已经产生的数预测下次所产生的数的“器”(器存在软件与硬件之分),真正的随机数生成器其产生的随机数具有随机性不可预测性不可重现性

什么是真正的随机数生成器?指的是由传感器采集设备外部温度、噪声等不可预测的自然量产生的随机数。比如Linux的/dev/random设备文件其根据设备中断(键盘中断、鼠标中断等)来产生随机数,由于鼠标的操作(移动方向、点击)是随机的、不可预测的也是不可重现的,所以产生的随机数是真随机数。/dev/random即所谓的随机数池,当通信过程(如https安全套接层SSL)需要加密密钥时,就从随机数池中取出所需长度的随机数作为密钥,这样的密钥就不会被攻击者(Attacker)猜测出。但是由于/dev/random是采集系统中断来生成随机数的,所以在无系统中断时,读取/dev/random是处于阻塞状态的,如下所示(鼠标移动与否决定了cat /dev/random的显示结果,cat /dev/random | od -x先显示的4行是查看该设备文件前,系统中断被采集而产生的随机数,而之后的随机数则是鼠标移动锁产生的随机数):

随机数生成器与线性同余法产生随机数_第1张图片

cat读取/dev/radom测试效果.gif

在Linux上还存在随机数生成器/dev/urandom,而读取该随机数池是不会阻塞的,因为其不受实时变化的因素影响,所以/dev/urandom是一个伪随机数生成器,而C语言的rand()库函数所产生的随机数也是伪随机数。/dev/random与/dev/urandom的区别在于一个阻塞一个非阻塞,一个更安全一个较安全。对于/dev/random来说,如果需要的随机数长度小于随机数池中的随机数,则直接返回获取到的随机数,并且池中的随机数长度减去获取长度,如果要获取的随机数长度大于池中已有的长度,则获取的进程处于阻塞状态等待新的生成的随机数部分注入池中。

2、伪随机数生成器:

所谓伪随机数生成器,指的是其生成的数在特定条件下是具有真随机数的特性的。伪随机数采用“软件算法+随机数种子”的方式来实现,算法一般不保密,而种子则必须要保密,其基本结构如下所示:

随机数生成器与线性同余法产生随机数_第2张图片

(图1)伪随机数生成器结构图

所谓种子类似于密码算法中的加密密钥,不同的种子(密钥)对应不同的随机数(密文)。内部状态指的是每一个状态下,随机数生成器中的数值状态,初始状态则是由种子来决定的。而将内部状态计算伪随机数的方法和内部状态改变的方法组合起来就是伪随机数生成器的算法

3、rand()函数采用的随机数生成器算法:

rand()函数是C语言的库函数,其产生随机数的算法为线性同余法(linear congruential method),其遵循“算法+种子”的伪随机数生成器结构,线性同余法的具体结构如下图所示:

随机数生成器与线性同余法产生随机数_第3张图片

(图2)线性同余法算法结构图

其基本伪代码为:

M = 正整数;
A = 大于0且小于M的正整数;
C = 大于0且小于M的正整数;
内部状态 = 种子seed;
while(true){
    伪随机数 = (A × 内部状态 + C) mod M;
    内部状态 = 伪随机数;
    输出伪随机数;
}

对于rand()函数来说,其种子seed常用当前时间戳(srand(time(NULL));)。由于对M取余,所以该随机数具有周期性,而M的大小决定了周期长短(M-1),而A和C也会影响周期(使得周期小于M-1),A、C、M的取值决定了产生的数是否具有不可预测性。比如当A=1,C=0,M=7时,产生的数只能是seed%7,周期为1。不具备随机数特征。所以A、C、M的选取是否得当十分重要(关于A、C、M、seed的取值如何才能比较好,可参考http://blog.csdn.net/memray/article/details/8932518)。

4、rand()与srand()函数代码实现:

srand()函数只是用种子初始化内部状态,因为srand()与rand()中都会对内部状态进行改变,所以内部状态的变量需要定义为静态全局变量。

VC中对于rand()的实现为:

//文件位置C:\Program Files\Microsoft Visual Studio\VC98\CRT\SRC\rand.c
#include 
#include 
#include 
#include 

#ifndef _MT
//MT为multithread(多线程)的缩写
//如果不是多线程则定义holdrand为内部状态变量
static long holdrand = 1L;
#endif  /* _MT */


void __cdecl srand (unsigned int seed)
{
    #ifdef _MT
        _getptd()->_holdrand = (unsigned long)seed;//线程中分别初始化
    #else
        holdrand = (long)seed;
    #endif
}

/**************************************************
其中:A=214013、C=2531011、M=65536(2^16)
在这里C=7 x 17 x 21269因此C与M是互为质数的,但是M比A和C都小,所以产生的随机数周期小于M
holdrand最初是seed,经过rand()之后holdrand就变为(holdrand × A + C) mod M
***************************************************/

int __cdecl rand (void)
{
    #ifdef _MT
        _ptiddata ptd = _getptd();//在不同线程中获取各自的内部状态,避免混淆
        return( ((ptd->_holdrand = ptd->_holdrand * 214013L + 2531011L) >> 16) & 0x7fff );
    #else
        return(((holdrand = holdrand * 214013L + 2531011L) >> 16) & 0x7fff);
    #endif
}

线性同余法只是常用在编程语言函数库中产生随机数(VC中的库函数是比较简单的版本,但是比较容易理解,glibc-2.21中的实现更为复杂,可参考http://ftp.gnu.org/gnu/glibc/glibc-2.21.tar.gz中glibc-2.21/stdlib/目录下的rand.c、rand_r.c和random.c、random_r.c),但是不适用于加密密钥的生成。因为当被攻击者获取到某些随机数之后,其种子seed以及A、C、M都会被反向计算出来,即有可能根据以往随机数预测出之后还未产生的随机数。而使用单向散列函数法(单向性作为不可预测的基础)、密码法(保密性作为不可预测的基础)、ANSI X9.17方法(PGP加密软件使用的伪随机数生成器算法,也是采用加密方式来确保随机数的不可预测性)的随机数生成器来确保随机数的不可预测性。

参考资料:《图解密码技术》

你可能感兴趣的:(Data,Structure,and,Algorithm,信息安全/密码技术)