使用/dev/random生成隨機數

不少庫例程產生的「隨機」數是準備用於仿真、遊戲等等;它們在被用於密鑰生成一類的安全函數時是不夠隨機的。其問題在於這些庫例程使用的算法的將來值能夠被攻擊者輕易地推導出來(雖然看起來它們多是隨機的)。對於安全函數,須要的隨機值應該是基於量子效應之類的確實沒法預測的值。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咱們並無關閉,請您根據本身需求在合適的地方關閉。dom

相關文章
相關標籤/搜索