從Linux內(nèi)核中獲取真隨機(jī)數(shù)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
內(nèi)核隨機(jī)數(shù)產(chǎn)生器
Linux內(nèi)核實(shí)現(xiàn)了一個(gè)隨機(jī)數(shù)產(chǎn)生器,從理論上說這個(gè)隨機(jī)數(shù)產(chǎn)生器產(chǎn)生的是真隨機(jī)數(shù)。與標(biāo)準(zhǔn)C庫中的rand(),srand()產(chǎn)生的偽隨機(jī)數(shù)不同,盡管偽隨機(jī)數(shù)帶有一定的隨機(jī)特征,但這些數(shù)字序列并非統(tǒng)計(jì)意義上的隨機(jī)數(shù)。也就是說它們是可重現(xiàn)的--只要每次使用相同的seed值,就能得到相同的偽隨機(jī)數(shù)列。通常通過使用time()的返回值來改變seed,以此得到不同的偽隨機(jī)數(shù)序列,但time()返回值的結(jié)果并不是不確定的(可預(yù)測),也就是這里仍然缺少一個(gè)不確定的噪聲源。對于需要真隨機(jī)數(shù)的程序,都不能允許使用偽隨機(jī)數(shù)。
為了獲得真正意義上的隨機(jī)數(shù),需要一個(gè)外部的噪聲源。Linux內(nèi)核找到了一個(gè)完美的噪聲源產(chǎn)生者--就是使用計(jì)算機(jī)的人。我們在使用計(jì)算機(jī)時(shí)敲擊鍵盤的時(shí)間間隔,移動(dòng)鼠標(biāo)的距離與間隔,特定中斷的時(shí)間間隔等等,這些對于計(jì)算機(jī)來講都是屬于非確定的和不可預(yù)測的。雖然計(jì)算機(jī)本身的行為完全由編程所控制,但人對外設(shè)硬件的操作具有很大的不確定性,而這些不確定性可以通過驅(qū)動(dòng)程序中注冊的中斷處理例程(ISR)獲取。內(nèi)核根據(jù)這些非確定性的設(shè)備事件維護(hù)著一個(gè)熵池,池中的數(shù)據(jù)是完全隨機(jī)的。當(dāng)有新的設(shè)備事件到來,內(nèi)核會估計(jì)新加入的數(shù)據(jù)的隨機(jī)性,當(dāng)我們從熵池中取出數(shù)據(jù)時(shí),內(nèi)核會減少熵的估計(jì)值。
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例程的函數(shù)。這里我們感興趣的地方是:如果ISR在注冊期間指定了 SA_SAMPLE_RANDOM標(biāo)志,在處理完action后,還要調(diào)用add_interrupt_randomness()這個(gè)函數(shù),它使用中斷間隔時(shí)間為內(nèi)核隨機(jī)數(shù)產(chǎn)生器產(chǎn)生熵。內(nèi)核就是在這里為熵池填充新數(shù)據(jù)的。
如果我們完全不操作計(jì)算機(jī)會如何呢?也就是作為噪聲源的產(chǎn)生者,我們完全不去碰鍵盤,鼠標(biāo)等外設(shè),不讓熵池獲得新的數(shù)據(jù),這個(gè)時(shí)候如果去熵池取數(shù)據(jù)內(nèi)核會如何反應(yīng)?
內(nèi)核在每次從熵池中取數(shù)據(jù)后都會減少熵的估計(jì)值,如果熵估計(jì)值等于0了,內(nèi)核此時(shí)可以拒絕用戶對隨機(jī)數(shù)的請求操作。
獲取內(nèi)核隨機(jī)數(shù)
有兩種方法可以從熵池中獲取內(nèi)核隨機(jī)數(shù)。一種是通過內(nèi)核導(dǎo)出的隨機(jī)數(shù)接口,另一種是通過特殊的設(shè)備文件/dev/random和/dev/urandom。下面分別討論兩種方法。
熵的輸出接口
?
1 void get_random_bytes(void *buf, int nbytes)
該函數(shù)返回長度為nbytes字節(jié)的緩沖區(qū)buf,無論熵估計(jì)是否為0都將返回?cái)?shù)據(jù)。使用這個(gè)函數(shù)時(shí)需要在內(nèi)核空間。我們寫一個(gè)小模塊來測試一下。
?
#include
#include
#include
#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
{
get_random_bytes(&randNum, sizeof(unsigned long));
printk(KERN_ALERT "We get random number: %ld\n", randNum);
}
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 [img]file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\)A[Y)I~](ZC9Z[3Y)IDK7LK.gif[/img]b/modules/$(KDIR)file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\OOY5$4OW5H`8`9%(9$)T67M.gifild M=$(PWD) modules
clean:
make -C [img]file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\)A[Y)I~](ZC9Z[3Y)IDK7LK.gif[/img]b/modules/$(KDIR)file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\OOY5$4OW5H`8`9%(9$)T67M.gifild M=$(PWD) clean
#end#
編譯之后加載模塊,通過dmesg命令輸出系統(tǒng)log最新的信息,可以看到我們的小模塊輸出了10個(gè)從內(nèi)核熵池中得到的隨機(jī)數(shù)。卸載模塊后再次加載可以重新獲取新的隨機(jī)數(shù),觀察輸出結(jié)果,與之前得到的隨機(jī)數(shù)完全不一樣。
[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[!--empirenews.page--]
[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
這兩個(gè)特殊設(shè)備都是字符型設(shè)備。我們可以在用戶空間通過read系統(tǒng)調(diào)用讀這兩個(gè)設(shè)備文件以此獲取隨機(jī)數(shù)。這兩個(gè)設(shè)備文件的區(qū)別在于:如果內(nèi)核熵池的估計(jì)值為0時(shí),
/dev/random將被阻塞,而/dev/urandom不會有這個(gè)限制。
#include
#include
#include
#include
#include
/* 從min和max中返回一個(gè)隨機(jī)值 */
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);
/* 因?yàn)槭菑?dev/random中讀取,read可能會被阻塞,一次讀取可能只能得到一個(gè)字節(jié),
* 循環(huán)是為了讓我們讀取足夠的字節(jié)數(shù)來填充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中獲取指定字節(jié)數(shù)的隨機(jī)值并寫入文件中保存--如果你需要以文件的形式提供隨機(jī)數(shù)的話。
dd if=/dev/urandom of = file count = 1 bs = bytes