Linux随机数发生器
日期:2017-11-29 01:42:10 星期三
一、源代码的基本情况
Linux内核版本
3.5.4
涉及文件
- /include/linux/random.h
- /drivers/char/random.c
功能概述
Linux内核随机数生成器是Linux操作系统内核的重要组成部分,该生成器从设备驱动等地方获取环境的随机噪声,从理论上返回难于预测的真随机数。该生成器的应用场景涵盖于具有较高安全性的协议栈,防止被黑客等进行爆破,如TCP序列号生成、DNS报文ID生成、TLS/SSL加密之类需要难以被预测的随机数场景。
二、外部访问接口
内核层输出接口
void get_random_bytes(void *buf, int nbytes);
void get_random_bytes_arch(void *buf, int nbytes);
get_random_bytes
,是一个内核接口,仅在内核编程中使用,会将随机数按照nbytes大小填充到buf分区,可用于TCP序列号的生成等。该函数为非阻塞,不会因为熵池的熵值不够而阻塞,所以会有一定的安全问题。
get_random_bytes_arch
,也是一个内核接口,使用的是硬件架构级别的随机数生成器,所以效率比上面软件生成的随机数高出好多。但是难以保证该硬件级别的随机数生成器是否足够安全。如果需要比较快速,而且相信该硬件随机数生成器的安全性话,是个不错的选择。如果硬件不支持,会调用get_random_bytes
函数。
用户层输出接口
除了上面两个内核接口,在用户层面也有相应的接口,就是linux中常见的/dev/urandom
和 /dev/random
这两个文件。在shell中,可以用dd和cat等命令直接获取随机数,也可以直接写个程序读取这两个文件即可。
# 从/dev/urandom处获取32个字节的随机数,并放置于本地的urand.txt文件
dd if=/dev/urandom of=./urand.txt count=1 bs=32
# 从/dev/random处获取32个字节的随机数,并放置于本地的rand.txt文件
dd if=/dev/random of=./rand.txt count=1 bs=32
/dev/random
适用于对随机数安全性要求比较高的情形,但为了保证安全,该文件的读取偶尔会阻塞等待熵池的熵值达到某个阈值。
/dev/urandom
没有上面的限制,不会因为熵值不够而导致阻塞,可以源源不断获取所需的随机数。但随着越来越多的随机数被获取,而且熵池的随机源没有被进一步补充,新产生的随机数的安全性就会有一定的下降,有可能被人攻破,但大多数的应用场景已经够用了。
环境噪音输入接口
void add_device_randomness(const void *, unsigned int);
void add_input_randomness(unsigned int type, unsigned int code, \
unsigned int value);
void add_interrupt_randomness(int irq, int irq_flags);
void add_disk_randomness(struct gendisk *disk);
add_device_randomness
,是一个内核接口,可将设备信息或者系统启动等信息添加到input和nonblocking的熵池中去,不同的机器会有不同的设备信息,如MAC地址、机器序列号等,主要是用于初始化这些熵池。为了避免拥有相同设备的机器带来的安全问题,该接口不增加熵池的熵值。
add_input_randomness
,也是一个内核接口,用于添加输入设备的随机性,该随机性包括输入事件本身的信息和输入的中断计时。
add_interrupt_randomness
,也是一个内核接口,使用中断计时作为随机源添加到熵池中。
add_disk_randomness
,也是一个内核接口,使用硬盘的寻道时间作为熵池的随机源,但对于高性能的固态硬盘来讲,寻道时间非常短,而且相对稳定,所以此时的寻道时间不适合用来作为随机源。
三、核心源码分析
随机数发生器理论
计算机作为一种可预测性较强的设备,很难生成真正的随机数,然而可以使用伪随机数算法来缓解这个问题。但伪随机数有很致命的一点,就是攻击者可以通过各种攻击手段猜到伪随机数发生器的序列,在一些应用场景下,使用这样的伪随机数是十分危险的。
为了解决上面的问题,我们可以从计算机的环境中收集“环境噪音”等外部攻击者难以获取的信息,然后把它们用于随机数的生成,就可以确保产生的随机数难以被预测。来自环境的随机性来源包括键盘、鼠标和某些中断的中断计时,以及其他非确定性特别强和难以被预测的事件。把这些来源的随机性使用类似CRC机制的方法加入到“熵池”中,这种CRC机制在密码学角度可能不是特别安全,但速度足够快,也可以在一定程度上避免被恶意加入熵。在加入熵的过程中,还会根据实际情况计算随机数产生器的内部状态中的熵值,即该熵值代表该系统的随机程度,侧面反映该系统此刻的安全性。
在获取随机数时,随机数是通过对熵池进行SHA哈希计算得到的。使用SHA哈希,是为了避免熵池的内部状态直接被外部获取,从而直接对后面的随机数进行预测。虽然目前没有人可以对SHA进行逆向,但是难以避免未来的技术可以攻陷。此时,要保证从随机发生器返回的数据量小于熵池内部状态的熵值,这样输出数据是完全不可预知的。同时,当输出数据时,熵池的熵值也要进行相应地降低。
熵池结构
熵池的结构,如上图所示,图片来源于[1],并对其进行一定的修改,使其更加符合本次阅读的源码结构。
linux内核随机数生成器有三个熵池组成,分别是input_pool
,blocking_pool
和nonblocking_pool
。每个熵池都有自己的熵值计数器,用于保存熵池的随机程度。如果有环境噪音流入,会先直接进入input_pool
中,同时会测量该环境噪音的熵值,并更新其计数器。blocking_pool
和nonblocking_pool
会根据具体需求,向input_pool
拉取熵,此时它们的熵值计数器也要有相应的增减。
熵池的数据结构如下:
struct entropy_store;
struct entropy_store {
/* read-only data: */
struct poolinfo *poolinfo;
__u32 *pool;
const char *name;
struct entropy_store