从Linux内核中获取真随机数

237 阅读7分钟
原文链接: click.aliyun.com

内核随机数产生器

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

 

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


                                        

                                            

                                            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。下面分别讨论两种方法。

熵的输出接口


                                            

                                                

                                                void 
                                                    get_random_bytes(void 
                                                        *buf, int 
                                                            nbytes)
                                                

                                            

                                        

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

 


                                        

                                            
#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如下:

 


                                        

                                            
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个从内核熵池中得到的随机数。卸载模块后再次加载可以重新获取新的随机数,观察输出结果,与之前得到的随机数完全不一样。

 


                                        

                                            
[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不会有这个限制。


                                            

                                                
#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。