很多庫例程產(chǎn)生的“隨機”數(shù)是準備用于仿真鳖轰、游戲等等庄拇;它們在被用于密鑰生成一類的安全函數(shù)時是不夠隨機的敲长。其問題在于這些庫例程使用的算法的未來值可以被攻擊者輕易地推導(dǎo)出來(雖然看起來它們可能是隨機的)技俐。對于安全函數(shù)鸣驱,需要的隨機值應(yīng)該是基于量子效應(yīng)之類的確實無法預(yù)測的值昼牛。Linux內(nèi)核(1.3.30以上)包括了一個隨機數(shù)發(fā)生器/dev/random术瓮,對于很多安全目的是足夠的。
/dev/random 是如何創(chuàng)建隨機數(shù)的呢贰健?
Linux 操作系統(tǒng)提供本質(zhì)上隨機(或者至少具有強烈隨機性的部件)的庫數(shù)據(jù)胞四。這些數(shù)據(jù)通常來自于設(shè)備驅(qū)動程序。例如伶椿,鍵盤驅(qū)動程序收集兩個按鍵之間時間的信息辜伟,然后將這個環(huán)境噪聲填入隨機數(shù)發(fā)生器庫。
隨機數(shù)據(jù)存儲在 熵池中悬垃,它在每次有新數(shù)據(jù)進入時進行“攪拌”游昼。這種攪拌實際上是一種數(shù)學轉(zhuǎn)換,幫助提高隨機性尝蠕。當數(shù)據(jù)添加到熵池中后烘豌,系統(tǒng)估計獲得了多少真正隨機位。
測定隨機性的總量是很重要的看彼。問題是某些量往往比起先考慮時看上去的隨機性小廊佩。例如囚聚,添加表示自從上次按鍵盤以來秒數(shù)的 32 位數(shù)實際上并沒有提供新的 32 位隨機信息,因為大多數(shù)按鍵都是很接近的标锄。
從 /dev/random 中讀取字節(jié)后顽铸,熵池就使用 MD5 算法進行密碼散列,該散列中的各個字節(jié)被轉(zhuǎn)換成數(shù)字料皇,然后返回谓松。
如果在熵池中沒有可用的隨機性位, /dev/random 在池中有足夠的隨機性之前等待践剂,不返回結(jié)果鬼譬。這意味著如果使用 /dev/random 來產(chǎn)生許多隨機數(shù),就會發(fā)現(xiàn)它太慢了逊脯,不夠?qū)嵱糜胖省N覀兘?jīng)常看到 /dev/random 生成幾十字節(jié)的數(shù)據(jù)军洼,然后在許多秒內(nèi)都不產(chǎn)生結(jié)果巩螃。
幸運的是有熵池的另一個接口可以繞過這個限制:/dev/urandom。即使熵池中沒有隨機性可用匕争,這個替代設(shè)備也總是返回隨機數(shù)避乏。如果您取出許多數(shù)而不給熵池足夠的時間重新充滿,就再也不能獲得各種來源的合用熵的好處了汗捡;但您仍可以從熵池的 MD5 散列中獲得非常好的隨機數(shù)淑际!這種方式的問題是,如果有任何人破解了 MD5 算法扇住,并通過查看輸出了解到有關(guān)散列輸入的信息,那么您的數(shù)就會立刻變得完全可預(yù)料盗胀。大多數(shù)專家都認為這種分析從計算角度來講是不可行的艘蹋。然而,仍然認為 /dev/urandom 比 /dev/random 要“不安全一些”(并通常值得懷疑)票灰。
應(yīng)用中出現(xiàn)的問題:
在我們的服務(wù)器程序中女阀,用戶登陸的時候會讀取/dev/random產(chǎn)生隨機數(shù),問題來了屑迂,當用戶登陸比較密集浸策,這時候read就會返回特別慢,并且返回的字節(jié)數(shù)也比要求的少惹盼,甚至不返回――阻塞庸汗。我們把用戶登陸處理函數(shù)放在了線程池里,導(dǎo)致的問題就是線程池里所有線程都可能會阻塞手报,這就造成了拒絕服務(wù)攻擊蚯舱。導(dǎo)致其他用戶登陸失敗改化。
CODE:
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/file.h>
6 #include <sys/time.h>
7 #include <errno.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10
11 static int get_random_fd (void)
12 {
13 static int fd = -2;
14
15 if (fd == -2)
16 {
17 fd = open ("/dev/random", O_RDONLY | O_NONBLOCK);
18 if (fd == -1)
19 fd = open ("/dev/urandom", O_RDONLY | O_NONBLOCK);
20 }
21
22 return fd;
23 }
24
25 /*
26 * Generate a series of random bytes. Use /dev/random if possible,
27 * and if not, use /dev/urandom.
28 */
29 void get_random_bytes(void* buf, int nbytes)
30 {
31 int i, fd = get_random_fd();
32 int lose_counter = 0;
33 char cp = (char)buf;
34 struct timeval tv;
35 static unsigned seed = 0;
36
37 if (fd >= 0)
38 {
39 while (nbytes > 0)
40 {
41 i = read (fd, cp, nbytes);
42 if ((i < 0) &&
43 ((errno == EINTR) || (errno == EAGAIN)))
44 continue;
45
46 if (i <= 0)
47 {
48 if (lose_counter++ == 8)
49 break;
50
51 continue;
52 }
53 nbytes -= i;
54 cp += i;
55 lose_counter = 0;
56 }
57 }
58
59 for (i = 0; i < nbytes; i++)
60 {
61 if (seed == 0)
62 {
63 gettimeofday(&tv, 0);
64 seed = (getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec;
65 }
66 *cp++ = rand_r(&seed) & 0xFF;
67 }
68
69 return;
70 }
解決方案:
1--3行: 定義fd為靜態(tài)變量,這樣只打開一次設(shè)備枉昏。
17 – 19行: 無阻塞模式打開/dev/random設(shè)備陈肛。如果該設(shè)備打開失敗嘗試打開/dev/urandom。
⌒至选29行: void get_random_bytes(void* buf, int nbytes)函數(shù)是提供給用戶的接口句旱,用戶調(diào)用這個函數(shù)就可以得到隨機數(shù)。
∥薄37-57行: read有可能返回的字節(jié)數(shù)小于請求的字節(jié)數(shù)谈撒。這時候就循環(huán)讀直到讀夠了所請求的大小。這樣最多重復(fù)8次畅涂。然后返回港华。
59-67行: 如果上面重復(fù)8次都沒有讀夠所請求的字節(jié)數(shù)午衰,則我們自己生成隨機數(shù)來填充立宜。
注意:打開的fd我們并沒有關(guān)閉,請您根據(jù)自己需求在合適的地方關(guān)閉臊岸。