http://www.linuxdiyf.com/viewarticle.php?id=17302
很多庫常式產生的“隨機”數是準備用於模擬、遊戲等等;它們在被用於密鑰產生一類的安全函數時是不夠隨機的。其問題在於這些庫常式使用的演算法的未來值可以被攻擊者輕易地推匯出來(雖然看起來它們可能是隨機的)。對於安全函數,需要的隨機值應該是基於量子效應之類的確實無法預測的值。Linux核心(1.3.30以上)包括了一個隨機數發生器/dev/random,對於很多安全目的是足夠的。
/dev/random 是如何建立隨機數的呢?
Linux 作業系統提供本質上隨機(或者至少具有強烈隨機性的組件)的庫資料。這些資料通常來自於裝置驅動程式。例如,鍵盤驅動程式收集兩個按鍵之間時間的資訊,然後將這個環境雜訊填入隨機數發生器庫。
隨機資料存放區在 熵池中,它在每次有新資料進入時進行“攪拌”。這種攪拌實際上是一種數學轉換,協助提高隨機性。當資料添加到熵池中後,系統估計獲得了多少真正隨機位。
測定隨機性的總量是很重要的。問題是某些量往往比起先考慮時看上去的隨機性小。例如,添加表示自從上次按鍵盤以來秒數的 32 位元實際上並沒有提供新的 32 位隨機資訊,因為大多數按鍵都是很接近的。
從 /dev/random 中讀取位元組後,熵池就使用 MD5 演算法進行密碼散列,該散列中的各個位元組被轉換成數字,然後返回。
如果在熵池中沒有可用的隨機性位, /dev/random 在池中有足夠的隨機性之前等待,不返回結果。這意味著如果使用 /dev/random 來產生許多隨機數,就會發現它太慢了,不夠實用。我們經常看到 /dev/random 產生幾十位元組的資料,然後在許多秒內都不產生結果。
幸運的是有熵池的另一個介面可以繞過這個限制:/dev/urandom。即使熵池中沒有隨機性可用,這個替代裝置也總是返回隨機數。如果您取出許多數而不給熵池足夠的時間重新充滿,就再也不能獲得各種來源的合用熵的好處了;但您仍可以從熵池的 MD5 散列中獲得非常好的隨機數!這種方式的問題是,如果有任何人破解了 MD5 演算法,並通過查看輸出瞭解到有關散列輸入的資訊,那麼您的數就會立刻變得完全可預料。大多數專家都認為這種分析從計算角度來講是不可行的。然而,仍然認為 /dev/urandom 比 /dev/random 要“不安全一些”(並通常值得懷疑)。
應用中出現的問題:
在我們的伺服器程式中,使用者登陸的時候會讀取/dev/random產生隨機數,問題來了,當使用者登陸比較密集,這時候read就會返回特別慢,並且返回的位元組數也比要求的少,甚至不返回――阻塞。我們把使用者登陸處理函數放在了線程池裡,導致的問題就是線程池裡所有線程都可能會阻塞,這就造成了拒絕服務的攻擊。導致其他使用者登陸失敗。
解決方案:
| 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 } |
13行: 定義fd為靜態變數,這樣只開啟一次裝置。
17 – 19行: 無阻塞模式開啟/dev/random裝置。如果該裝置開啟失敗嘗試開啟/dev/urandom。
29行: void get_random_bytes(void* buf, int nbytes)函數是提供給使用者的介面,使用者調用這個函數就可以得到隨機數。
37-57行: read有可能返回的位元組數小於請求的位元組數。這時候就迴圈讀直到讀夠了所請求的大小。這樣最多重複8次。然後返回。
59-67行: 如果上面重複8次都沒有讀夠所請求的位元組數,則我們自己產生隨機數來填充。
注意:開啟的fd我們並沒有關閉,請您根據自己需求在合適的地方關閉。