從Linux核心中獲取真隨機數

maojunxu發表於2018-03-09

核心隨機數產生器

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.
"
);
    for (i=0; i<NUM; i++)
    {
        get_random_bytes(&randNum[i], sizeof(unsigned long));
        printk(KERN_ALERT "We get random number: %ld
"
, randNum[i]);
    }
    return 0;
}
 
static void random_exit(void)
{
    printk(KERN_ALERT "quit get_random_num.
"
);
}
 
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。


相關文章