每次在生成随机数(串)的时候都特别纠结不知道该使用哪种方式,今天看翻看一个框架的时候发现其通过直接读取/dev/urandom
获取。
urandom
是linux
系统向用户模式程序输出随机序列的字符接口,通过它来获取不失为一个很好的办法。
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
bool generate(void *ptr,unsigned len)
{
if(len == 0)
return true;
int n = 0;
int fd = open("/dev/urandom",O_RDONLY);
if(!fd) {
printf("Failed to open /dev/urandom");
return false;
}
while(len > 0) {
n = read(fd,ptr,len);
if(n < 0 && errno == EINTR)
continue;
if(n <= 0)
break;
ptr = (char*)ptr + n;
len -= n;
}
close(fd);
if(len > 0) {
printf("Failed to read /dev/urandom");
return false;
}
return true;
}
我们知道,linux
提供的随机序列字符设备有两个,分别为/dev/random
和/dev/urandom
。但是代码里为什么就使用urandom
而不是random
呢?是不是两者可以互换呢?
答案是否定的,无论什么时候,都强烈建议使用urandom
,但是有些情况需要留意。那么random
和urandom
有什么哪些区别呢?
要说明他俩的区别,先需要了解熵这个概念。
熵
Linux
内核采用熵
来描述数据的随机性。熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大。在信息学中,熵被用来表征一个符号或系统的不确定性,熵越大,表明系统所含有用信息量越少,不确定度越大。
计算机本身是可预测的系统,因此,用计算机算法不可能产生真正的随机数。但是机器的环境中充满了各种各样的噪音,如:硬件设备发生中断的时间
,用户点击鼠标的时间间隔
等是完全随机的,事先无法预测。Linux
内核实现的随机数生成器正是利用系统中的这些随机噪音来产生高质量随机数序列。
内核维护了一个熵池
用来收集来自设备驱动程序
和其它来源的环境噪音
。理论上,熵池中的数据是完全随机的,可以实现产生真随机数序列。为跟踪熵池中数据的随机性,内核在将数据加入池的时候将估算数据的随机性,这个过程称作熵估算
。熵估算值描述池中包含的随机数位数,其值越大表示池中数据的随机性越好。
查看系统中的熵
通过下面命令可以查看系统熵池的容量:
~# cat /proc/sys/kernel/random/poolsize
4096
通过下面命令可以查看系统熵池中拥有的熵数:
~# cat /proc/sys/kernel/random/entropy_avail
3788
通过下面命令可以查看从熵池中读取熵的阀值,当entropy_avail
中的值少于这个阀值,这读取/dev/random
会被阻塞:
~# cat /proc/sys/kernel/random/read_wakeup_threshold
64
random
与urandom
的区别
首先/dev/urandom
和/dev/random
提供相同的随机性。/dev/random
并不提供"真正的随机"数据,对于"密码学"通常不需要"真正的随机"。Linux
内核CSPRNG
定期(通过收集更多的熵)对其自身进行密钥更改。但/dev/random
还会跟踪内核池中还剩多少熵,且如果剩余的熵不足还会进行阻塞。然而这样的设计并没有为数据的随机性作出贡献。而/dev/urandom
,它根据一个初始的随机种子(这个种子来源就是熵池中的熵)来产生一系列的伪随机数,而并不会在熵耗尽的情况下阻塞。
但是,Linux
下的/dev/urandom
有一个不足之处,那就是:如果在系统启动阶段使用,熵池中可能还不存在任何熵,这时候/dev/urandom
产生的随机数是可预测的!虽然各linux
发行版本会在系统中保存一些随机数,但是,系统崩溃或者刚安装的系统上这没有帮助。
所以除非要在启动启动阶段产生随机数,否则绝大多数情况下还是使用/dev/urandom
来产生随机数,这样才不会引起程序莫名的挂起。
注:
从`linux 3.17`开始,提供了系统调用:`getrandom(2)`
该系统调用执行正确的操作:阻塞直到收集到足够的初始熵为止,此后不再阻塞。