日期:2014-05-16  浏览次数:20825 次

Linux下随机数生成的常见方法

     众所周知,利用Linux下的rand函数可以生成范围在0到RAND_MAX(在stdlib.h中定义,值为2147483647)的数值,但是一般来讲,为了达到更好的随机效果,需要利用srand函数设置相应的随机种子(或者说随机数的起始值),种子相同,所产生的随机数也是相同的,因此,要想获得随机效果好的随机数,一定要保证每次的随机种子有差别。常见的可作为随机种子的有:当前时间、/dev/random或/dev/urandom文件内容以及uuid码。

         在阐述各种随机种子产生之前,有必要先介绍下srand函数,它是ISO C下的产生随机数的标准函数,BSD下与之相对应的函数是random。srand函数需要接收的参数是一个无符号整型数据

一、当前时间:

         利用time函数每次获取当前的系统时间,并且也保证了每次的随机种子有差异,实验证明它是一种最为简介,同时也是使用最为广泛的方法,但是,针对某些特定环境,未必会产生有效的随机种子。在本篇日志中,我们试图将这三种产生随机种子的方法应用于这样一种特殊环境:在嵌入式环境下,试图在开发板启动之后、系统时间没有同步之前这段时间去产生随机种子,并检验所产生的随机种子的有效性,值得说明的是,此种开发板不会去保存系统时间,每次启动之后都会从一个默认时间开始计时。通过实验得出,当前时间运用在这种环境下完全不能达到产生随意随机数的目的,因为嵌入式开发板的特殊性,在每次开发板运行到产生随机数的程序时,此时的当前时间都是一样的,于是每次重新启动开发板所产生的随机数也是一样的。

二、/dev/random以及/dev/urandom:

         /dev/random以及/dev/urandom产生随机种子的原理是利用当前系统的熵池来计算出一定数量的随机比特,其中熵池是根据当前系统的“环境噪音”,它是由很多参数共同评估的,如内存的使用,文件使用量等等,环境噪音直接影响着所产生的随机种子的有效性。同样,若我们试图运用于前面所说的特殊环境,是否能够产生满足所需的随机种子呢?实验证明,能够保证每次开发板在每次启动之后能获取到不同的随机数。毕竟环境噪音所选取的评估参数众多,会导致所获取到的种子有很大的差异性。

         下面试图阐述一下/dev/random与/dev/urandom之间的区别。根据维基百科所述,urandom即”unlocked random”,,每次打开并读取/dev/urandom时,会从熵池中随机返回所需要的字节数,/dev/urandom的读取操作不会阻塞,因为它会重复使用熵池中的数据以产生随机数;反之/dev/random则是每次读之前去检查熵池是否为空,若为空,则需要阻塞并去更新熵池。从这点可以看出,前者不需要阻塞,但是随机效果弱于后者,因此常用于弱密码系统。

         相关代码如下:

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{

    FILE *fs_p = NULL;
    unsigned int seed = 0;
    
    fs_p = fopen ("/dev/urandom", "r");
    if (NULL == fs_p) 
    {
        printf("Can not open /dev/urandom\n");
        return -1;
    }
    else
    {
      fread(&seed, sizeof(int), 1, fs_p);  //obtain one unsigned int data 
      fclose(fs_p);
    }
   
    srand(seed);

    printf("Random numner is %u.\n", rand());
    return 0;
}

三、UUID码:      

uuid码的全程是通用唯一识别码,它是一个软件建构的标准,设置uuid码的目的是让分布式系统中的每一个元素都有唯一识别的信息,它是由32个16进制数据组成,并用”-”连接号分成五段,形成8-4-4-4-12形式的数据,理论上来讲,uuid的总数是2128。Linux下的uuid码是由内核提供的,存在/proc/sys/kernel/random/uuid文件里,从维基百科了解到,生成uuid的方法众多,至今出现了5个版本,MAC地址、DCE加密、MD5 hash、随机数机制以及SHA-1 hash(最新版本5,在RFC4122中有阐述)。既然我们已经获取到了唯一的uuid码,那如何转换成我们实际srand函数所需的无符号整数呢?

    可以采取的措施有:从/proc/sys/kernel/random/uuid文件的CRC校验码中抽取最多10位,并且小于2147483647的数据;直接抽取文件内容中的数值位,但要保证其最多10位,并且值小于2147483647.下面代码模拟了后者:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define FILENAME "/proc/sys/kernel/random/uuid"
#define MAX_SEED 10
#define UUID_LENGTH 35
#define UUID_DELIM "-"

void check_rand_number(char *seed, char *p)
{
  char *tmp_seed = "2147483647"; //MAX_RAND
  char *tmp = seed;

  //fill the new number to seed array
  *(seed + strlen(seed)) = *p;  
  if (strcmp(tmp_seed, tmp) >= 0)
  {
    return ;
  }
  else
  {
    *(seed + strlen(seed) - 1) = '\0'; 
  }
}

void parse_uuid_and_save(FILE *fs_p, char *seed)
{
  char uuid[UUID_LENGTH + 1] = {0};
  char *p = NULL;

  //UUID fo