内核随机数产生器

Linux内核实现了一个随机数产生器,从理论上说这个随机数产生器产生的是真随机数。与标准C库中的rand(),srand()产生的伪随机数不同,尽管伪随机数带有一定的随机特征,但这些数字序列并非统计意义上的随机数。也就是说它们是可重现的--只要每次使用相同的seed值,就能得到相同的伪随机数列。通常通过使用time()的返回值来改变seed,以此得到不同的伪随机数序列,但time()返回值的结果并不是不确定的(可预测),也就是这里仍然缺少一个不确定的噪声源。对于需要真随机数的程序,都不能允许使用伪随机数。

为了获得真正意义上的随机数,需要一个外部的噪声源。Linux内核找到了一个完美的噪声源产生者--就是使用计算机的人。我们在使用计算机时敲击键盘的时间间隔,移动鼠标的距离与间隔,特定中断的时间间隔等等,这些对于计算机来讲都是属于非确定的和不可预测的。虽然计算机本身的行为完全由编程所控制,但人对外设硬件的操作具有很大的不确定性,而这些不确定性可以通过驱动程序中注册的中断处理例程(ISR)获取。内核根据这些非确定性的设备事件维护着一个熵池,池中的数据是完全随机的。当有新的设备事件到来,内核会估计新加入的数据的随机性,当我们从熵池中取出数据时,内核会减少熵的估计值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
asmlinkage int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
                            struct irqaction *action)
{
    int status = 1;
    int retval = 0;
     
    if (!(action->flags & SA_INTERRUPT))
        local_irq_enable();
 
    do
    {
        status |= action->flags;
        retval |= action->handler(irq, action->dev_id, regs);
        action = action->next;
    }while (action);
     
    if (status & SA_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
 
    local_irq_disable();
    return retval;
}

上面这段代码是x86上用来处理某条中断线上注册的ISR例程的函数。这里我们感兴趣的地方是:如果ISR在注册期间指定了SA_SAMPLE_RANDOM标志,在处理完action后,还要调用add_interrupt_randomness()这个函数,它使用中断间隔时间为内核随机数产生器产生熵。内核就是在这里为熵池填充新数据的。

如果我们完全不操作计算机会如何呢?也就是作为噪声源的产生者,我们完全不去碰键盘,鼠标等外设,不让熵池获得新的数据,这个时候如果去熵池取数据内核会如何反应?

内核在每次从熵池中取数据后都会减少熵的估计值,如果熵估计值等于0了,内核此时可以拒绝用户对随机数的请求操作。

获取内核随机数

有两种方法可以从熵池中获取内核随机数。一种是通过内核导出的随机数接口,另一种是通过特殊的设备文件/dev/random和/dev/urandom。下面分别讨论两种方法。

熵的输出接口

1
void get_random_bytes(void *buf, int nbytes)

该函数返回长度为nbytes字节的缓冲区buf,无论熵估计是否为0都将返回数据。使用这个函数时需要在内核空间。我们写一个小模块来测试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#define NUM 10
 
void get_random_bytes(void *buf, int nbytes);
 
static int get_random_number(void)
{
    unsigned long randNum[10];
    int i = 0;
 
    printk(KERN_ALERT "Get some real random number.\n");
    for (i=0; i<NUM; i++)
    {
        get_random_bytes(&randNum[i], sizeof(unsigned long));
        printk(KERN_ALERT "We get random number: %ld\n", randNum[i]);
    }
    return 0;
}
 
static void random_exit(void)
{
    printk(KERN_ALERT "quit get_random_num.\n");
}
 
module_init(get_random_number);
module_exit(random_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Test");

Makefile如下:

1
2
3
4
5
6
7
8
9
10
obj-m = get_random_num.o
KDIR = $(shell uname -r)
PWD = $(shell pwd)
 
all:
    make -C /lib/modules/$(KDIR)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(KDIR)/build M=$(PWD) clean
 
#end#

编译之后加载模块,通过dmesg命令输出系统log最新的信息,可以看到我们的小模块输出了10个从内核熵池中得到的随机数。卸载模块后再次加载可以重新获取新的随机数,观察输出结果,与之前得到的随机数完全不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[37972.467955] Get some real random number.
[37972.468392] We get random number: -82199505
[37972.468580] We get random number: -276237802
[37972.468586] We get random number: 411869317
[37972.468590] We get random number: 1779353222
[37972.468594] We get random number: 823507551
[37972.468598] We get random number: 1061461415
[37972.468602] We get random number: 1372137935
[37972.468606] We get random number: 1460835009
[37972.468610] We get random number: 2002191729
[37972.468614] We get random number: -272204344
[38059.349589] quit get_random_num.
[38070.575433] Get some real random number.
[38070.575462] We get random number: 1111808207
[38070.575476] We get random number: -13789055
[38070.575481] We get random number: 240443446
[38070.575485] We get random number: -606998911
[38070.575489] We get random number: 538794850
[38070.575493] We get random number: -500786675
[38070.575497] We get random number: -1240394927
[38070.575501] We get random number: 1233931345
[38070.575504] We get random number: 1488497117
[38070.575508] We get random number: -177688514

/dev/random & /dev/urandom

这两个特殊设备都是字符型设备。我们可以在用户空间通过read系统调用读这两个设备文件以此获取随机数。这两个设备文件的区别在于:如果内核熵池的估计值为0时,

/dev/random将被阻塞,而/dev/urandom不会有这个限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
 
/* 从min和max中返回一个随机值 */
 
int random_number(int min, int max)
{
    static int dev_random_fd = -1;
    char *next_random_byte;
    int bytes_to_read;
    unsigned random_value;
     
    assert(max > min);
     
    if (dev_random_fd == -1)
    {
        dev_random_fd = open("/dev/random", O_RDONLY);
        assert(dev_random_fd != -1);
    }
     
    next_random_byte = (char *)&random_value;
    bytes_to_read = sizeof(random_value);
     
    /* 因为是从/dev/random中读取,read可能会被阻塞,一次读取可能只能得到一个字节,
     * 循环是为了让我们读取足够的字节数来填充random_value.
     */
    do
    {
        int bytes_read;
        bytes_read = read(dev_random_fd, next_random_byte, bytes_to_read);
        bytes_to_read -= bytes_read;
        next_random_byte += bytes_read;
    }while(bytes_to_read > 0);
     
    return min + (random_value % (max - min + 1));
}

同样,还可以用dd命令从/dev/urandom中获取指定字节数的随机值并写入文件中保存--如果你需要以文件的形式提供随机数的话。

dd if=/dev/urandom of = file count = 1 bs = bytes

 

关于内核随机数产生器的详细介绍,可参考Linux内核设计与实现第二版附录B。

从Linux内核中获取真随机数的更多相关文章

  1. 从Linux内核中获取真随机数【转】

    转自:http://www.cnblogs.com/bigship/archive/2010/04/04/1704228.html 内核随机数产生器 Linux内核实现了一个随机数产生器,从理论上说这 ...

  2. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  3. Linux内核中双向链表的经典实现

    概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...

  4. Linux内核中的fastcall和asmlinkage宏

    代码中看见:#define _fastcall 所以了解下fastcall -------------------------------------------------------------- ...

  5. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  6. Linux内核中影响tcp三次握手的一些协议配置

    在Linux的发行版本中,都存在一个/proc/目录,有的也称它为Proc文件系统.在 /proc 虚拟文件系统中存在一些可调节的内核参数.这个文件系统中的每个文件都表示一个或多个参数,它们可以通过 ...

  7. Linux内核中流量控制

    linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...

  8. Linux内核中SPI/I2c子系统剖析

    Linux内核中,SPI和I2C两个子系统的软件架构是一致的,且Linux内核的驱动模型都以bus,driver,device三种抽象对象为基本元素构建起来.下文的分析将主要用这三种抽象对象的创建过程 ...

  9. Linux内核中链表实现

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: struct list_node{ stuct list_node *pre; stuct list_node *next; ElemTy ...

随机推荐

  1. Go:错误处理

    在默认情况下,当程序发生错误(panic)后,程序就会退出(崩溃),所以我们希望,当程序发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行.比如捕获到错误后,打入日志或给管理员一个提示(邮件 ...

  2. 监听服务器 重启apache

    import requests import os import time url = 'http://www.ydyigo.com/findPwd.php' def get_server_statu ...

  3. CSS九宫格样式

    CSS .main>div { width: 14%; min-width: 160px; padding: 2%; height: 60px; border: 1px solid #f4f4f ...

  4. http chunked 理解

    https://imququ.com/post/transfer-encoding-header-in-http.html #! /usr/bin/python #coding:utf8 import ...

  5. CentOS7 Failed to start LSB: Bring up/down解决方法(真正有效的方法)

    刚刚装好的虚拟机突然不能上网了,报错很诡异,具体报错如下: /etc/init.d/network restart Restarting network (via systemctl):  Job f ...

  6. Discuz论坛广告横幅大图在百度app内无法显示,百度app默认开启了广告屏蔽

    问题由来 前段时间搭的一个Discuz论坛上挂了2个广告横幅,网站的话收录还不错,然后客户就反应百度app上无法看到横幅. 由于我没有下载百度app,看不到效果我将信将疑,因为电脑,手机浏览器都是ok ...

  7. STM32F407 按键输入实验 库函数版 个人笔记

    读取IO口输入的函数: STM32F4 的 IO口做输入使用的时候,是通过调用函数 GPIO_ReadInputDataBit()来读取 IO 口的状态的.了解了这点,就可以开始我们的代码编写了. 按 ...

  8. Linux学习笔记02

    1.文件处理命令:touch touch [文件目录][文件名] 如:touch /tmp/jiejie/jiejie.conf2.文件处理命令:cat cat 参数[文件目录][文件名]//显示文件 ...

  9. 创建私有 Gems 源

    1.安装依赖 yum install gem -y gem install builder   2.安装.配置nginx的文件列表 添加/etc/nginx/default.d/mirrors.con ...

  10. [luoguP3402] 最长公共子序列(DP + 离散化 + 树状数组)

    传送门 比 P1439 排列LCS问题,难那么一点点,只不过有的元素不是两个串都有,还有数据范围变大,树状数组得打离散化. 不过如果用栈+二分的话还是一样的. ——代码 #include <cs ...