http://blog.csdn.net/x86/archive/2008/04/07/2257255.aspx
关于linux下的随机数
在linux下取随机数,当然可以简单的用rand函数,不过要注意的是一定要设置好种子,否则伪随机数就会变成非常伪的随机数。设置种子,一般就用time函数返回当前时间即可。一般来讲,这样的做法基本上就可以了,因为虽然我们用的是随机数,但是由于种子不同,从上亿的数中去猜测我们的种子几乎是不可能的。
但是如果我们的种子算法被知道了,那么显然就不行了。当然作为某些应用也无所谓,比如我们要在屏幕上随机的画一只小猪。这样的应用几乎不会有谁会去关心下一次会是什么结果。不过有些应用就不一样了,大的不说,就是一些游戏,也得考虑随机数的安全性问题。
简单的办法是,我们的种子也用随机数来表示。不过这样一来,似乎就有鸡生蛋还是蛋生鸡的问题。好在linux给我们提供了“真正的”随机数,在内核中,linux会维护一些偶然出现的数据,并且为用户提供访问接口。之所以称之为真正的随机数,是因为这些数据来源于计算机本身的偶然操作,比如硬盘操作、键盘和鼠标的操作,等等,这些操作比起那些通过固定算法生成的伪随机数来说,当然是更真实一些了,在这里我们有一个很酷的名字叫做“熵”。内核提供的接口是/dev/random和/dev/urandom设备,二者的区别是读取时random肯定会返回一个数,如果没有足够的数据,就会阻塞。而urandom则不会阻塞,但是不保证返回的是合适的数据。
下面的代码中,函数init_random用来生成一个随机的种子,之后直接调rand就可以得到随机数。之所以读了512次,然后全部组合在一起,就是因为urandom设备不保证每一次读到的都是真实的数据。
另一个函数my_rand则是通过直接去读random设备来得到真实的随机数。这样每一次都是真正的随机数,但是问题在于如果系统的“熵”不够,那么程序就会阻塞。对于安全性要求比较高的应用来说,可以使用这样的方式。如果“熵”不够,可以人为的去“制造”一些熵。比如下面的程序,如果你不做任何操作,也许输出几个随机数之后程序就会停止输出,这是你在另一个终端运行一些比较繁忙的进程,比如"find /",就会发现我们的程序又开始源源不断的输出随机数。
使用random设备的例子很多,比如gpg就会在生成key的时候让你不断的敲键盘直到它满意为止。对于2.4以后的内核,你还可以通过proc文件系统的接口得到random设备的更多信息,比如 /proc/sys/kernel/random/entropy_avail可以知道系统中还有多少“熵”可以用。运行我们的例子可以发现,这个值一下子就减少到个位数,直到程序阻塞。另外一个文件也很有意思,那就是/proc/sys/kernel/random/uuid,通过这个接口可以很容易的得到真正唯一的uuid。
关于random设备还有一些可说的,那就是系统安全。有人居然能通过系统启动过程或者系统运行的某段时间产生的熵的内容来破译出某些信息,这听起来相当高深,不过理论上来讲却完全可能。因为random设备虽然是通过系统噪声来得到熵,但是如果两次系统启动完全一致,那么启动过程中生成的熵当然会完全一致。不过这些安全漏洞不用我们担心,因为现在的系统都有相关的补丁。
#include <stdio.h>
#include <sys/time.h>
#include <fcntl.h>
void init_random ()
...{
unsigned int ticks;
struct timeval tv;
int fd;
gettimeofday (&tv, NULL);
ticks = tv.tv_sec + tv.tv_usec;
fd = open ("/dev/urandom", O_RDONLY);
if (fd > 0)
...{
unsigned int r;
int i;
for (i = 0; i < 512; i++)
...{
read (fd, &r, sizeof (r));
ticks += r;
}
close (fd);
}
srand (ticks);
printf("init finished ");
}
unsigned int new_rand ()
...{
int fd;
unsigned int n = 0;
fd = open ("/dev/urandom", O_RDONLY);
if (fd > 0)
...{
read (fd, &n, sizeof (n));
}
close (fd);
return n;
}
int main ()
...{
int n, i;
init_random ();
n = rand ();
printf ("n=%d ", n);
for(i=0;i<10;i++)
printf ("%u ", new_rand());
}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2257255
[收藏到我的网摘] [发送Trackback] x86发表于 2008年04月07日 15:23:45
评论
# oldbeggar 发表于2008-04-08 00:47:40 IP: 118.249.6.*
建议楼主查一下文献,Linux的C随机函数是非常差的。
不建议在真正对随机要求很高的项目中使用~~
# x86 发表于2008-04-08 09:30:32 IP: 203.187.169.*
我这里说的不是C的随机函数。
# oldbeggar 发表于2008-04-08 18:45:40 IP: 61.187.64.*
明白,我意思是你介绍的这些方法如果基于一个不太完善的随机数生成器会产生漏洞吗?
# x86 发表于2008-04-09 10:14:07 IP: 203.187.169.*
因为“熵”的数据与系统有关,比如磁盘读取之类的操作,理论上来讲可能会有安全上的问题。不过实际操作上应该不是很简单,因为即使是内核从各种系统“噪声”来生成“熵”池中的数据,但是也是有算法的,还要评估一个数据是否足够随机,比如按一个键多次并不会产生多次随机数据。
至于随机数生成器本身因该不会有安全漏洞方面的问题,因为只是一个与系统无关的算法,数学上的东西多一些。漏洞主要是指上述的“熵”,据说kernel还专门为这个问题做了改进。
你说的C随机函数非常差,那是因为算法本身就是伪随机的,而“熵”则是改进了这个问题。
# fangzhe 发表于2008-04-09 22:09:17 IP: 221.239.196.*
其实x86的本意比较正确,越说越糊涂,理一理:
抛随机数,从信息论的角度看就是需要熵。但一般随机数都是由一定的“伪随机”算法(PRNG)抛出来的(参考“混沌”),抛这个需要“种子”——这个就是算法唯一需要熵,也是唯一真正随机的地方。给同一个算法同一个种子,出来的随机数也是相同的。
Linux是从用户的操作,包括磁盘、网络等等“随机事件”产生熵,/dev/random便是直接从中取(所以没有足够的就阻塞),而/dev/urandom则是从系统维护的一个熵池中取,也就是低一个档次的随机。
BSD多是用的Yarrow算法,当然这是个伪随机算法。但是这不一定会差,因为其实只要这个伪随机算法足够厉害,并且攻击者不知道系统内部运转的状态(即:除非获得root级权限),那么这样抛出来的随机数也是安全的。一般我们种子选time(0)就足够安全,因为每个机器的时间是存在微小差别的,而且攻击者并不知道程序产生种子的确切时刻。
C库里那个函数不好,是说算法不好——那个方法为了快,所以抛出来的随机数,分布不是很好。所以这并不能靠改种子的办法解决,倒是换个随机数生成方法更方便。
# x86 发表于2008-04-10 09:51:53 IP: 203.187.169.*
楼上说的没错。
记得看过一篇文章,/dev/urandom在碰到熵池不够的情况,内核会根据已有的熵计算出新的数据,比如通过若干已有的随机数据算一个md5,这样的数据虽然不是真正的随机数据,但是也足可以用了。