密碼學裏的隨機數發生器[詳細]

創建時間:2002-09-23
文章屬性:翻譯
文章來源:Phrack 59  0x0f
文章提交:backup (g0200685_at_nus.edu.sg)

==Phrack Inc.==

           Volume 0x0b, Issue 0x3b, Phile #0x0f of 0x12


|=--------=[ CRYPTOGRAPHIC RANDOM NUMBER GENERATORS ]=--------=|
|=------------------------------------------------------------=|
|=----------=[Author: DrMungkee <[email protected]> ]=--------=|
|=------------------------------------------------------------=|
|=----------=[Translator: winewind <[email protected]>]=-------=|



密碼學裏的隨機數發生器

----| 介紹

對於密碼系統的安全性來說,每個組件都是很重要的。一個組件設計的失敗可能使其他所有的組件崩潰。密碼隨機數常常被用作密鑰,補充信息,輔助信息和初始化向量。對每一個組件來說,使用一個好的隨機數發生器(RNG)是必要的。利用計算機行爲的可預測性,可以人爲的製造很多複雜因素。本文的範圍涵括了隨時數產生器的設計,執行和分析。將要涉及的隨機數發生器(RNG)包括:NoiseSpunge, Intel 隨機數發生器(Intel RNG),Linux下的“/dev/random”和Yarrow。


----| 關鍵詞:

RNG - 隨機數發生器(Random Number Generator)
PRNG - 僞隨機數發生器(Pseudo Random Number Generator)
信息熵(entropy) - 不可預知信息(Unpredictable information)
冗餘信息(redundancy)- 可預知信息(Predictable or probabilistic information)

(譯者注:entropy一詞源於物理,是“熵”的意思。在信息技術中引入,從而有了“信息熵”的說法。“熵”的特性:在封閉系統中,系統的熵只會增加或保持不變,系統的平衡點是熵最大的時候。無線電中常翻譯成“平均信息量”。我也不知道這裏用信息熵的說法是否便於理解。如果覺得彆扭,就理解成一種信息好了)

(譯者注:這裏我加入一段引來的文章,可能會讓大家對這個名詞瞭解得更好:現代信息學的基礎是信息熵理論,即對被傳送信息進行度量的一種平均值,單位是比特。四十年代,現代信息論創始人、美國貝爾實驗室科學家閃農(C.Shannon)發明了信息熵理論,由此提出了數據優化編碼、輸入輸出效率、通訊傳遞渠道效率、多餘度和數據壓縮等一系列信息科學基礎理論和技術。信息熵是信息產業的地基。比如,不管計算機硬件軟件如何更新換代,英文的字符平均信息熵(靜態信息熵)是4.03比特,因而,處理和儲存英文數據的每個字符的編碼不能少於5比特;中文的漢字平均信息熵是9.65比特,因而,處理和儲存中文數據的每個字符的編碼不能少於10比特。)


1.0) 概述

設計一個隨機數發生器(RNG)需要考慮各種因素。在白噪音環境中(譯者注:在一定帶寬內,在各個頻率上,各種噪音的強度都是一樣的),輸出必須是不可識別的。輸出的任何一部分都是不可預知的。而且基於已知的結果,無法推算出上一步(不可逆)和下一步的結果。如果一個隨機數產生器不能遵照這個標準,那麼產生的密碼是不安全的。

1.1) 信息熵(entropy)的收集:

爲了滿足第一條和第二條要求,爲這些信息熵找尋好的來源(信息源)就成了一個首選任務。這些信息源對於攻擊者必須是不可監測的。而且任何對信息源的操作對攻擊者來說都是不可知和無法重複的。

鼠標的移動常常被用作信息熵的一種。但是如果RNG不能正確的處理信息熵,將會產生大量的冗餘信息。舉個例子,鼠標移動的時間間隔是100毫秒。當鼠標在各方向快速移動時,其位置記錄如下:


      X-軸               Y-軸
   0000001011110101   0000000100101100    注意:在各個座標中只
   0000001000000001   0000000100001110    有最後九位是隨機的。
   0000001101011111   0000001001101001    
   0000001000100111   0000000111100100
   0000001010101100   0000000011111110
   0000000010000000   0000000111010011
   0000001000111000   0000000100100111
   0000000010001110   0000000100001111
   0000000111010100   0000000011111000
   0000000111100011   0000000100101010

下面的例子演示了一個更加實際的信息熵的收集過程。保留X-座標和Y-座標的最後四位(因爲它們最重要),和採樣得到的時間作異或運算,並把它們隨意排列如下:

    X      Y       時間      異或後
   1010   1001   00100110   01111111
   0100   1100   00101010   00000110
   0101   0010   01011111   01110101
   1001   1100   10110000   11111100
   0101   0100   11001110   11100010
   0101   1100   01010000   01111100
   1011   0000   01000100   00011100
   0111   0111   00010111   00101000
   0011   0101   01101011   01110110
   0001   0001   11011000   11010001

這是一個好辦法,因爲從各座標得到的四位數代表了在16像素平面上的任意方向(譯者注:上面的數據表明,這個範圍對紀錄鼠標的運動已經足夠大了),而不是 256*256平面上65536種移動方向。選用時間是因爲雖然它們是連續的,但是它們的最後八位在CPU時鐘週期內非常頻繁的變化,這些比特位是無法人爲預料的。異或運算用於合併兩方面的信息熵。在合併數字並且保留各比特位的獨立上,異或是個好辦法:)

最常見的信息源包括用戶的人爲反應或某種經過排列變形後的高頻時鐘的序列。把上面兩種合二爲一得到的結果往往就是我們所需要的。用高精度採集用戶觸發事件的反應時間(擊鍵,磁盤輸入/輸出,中斷,鼠標點擊)的方法有其優越性,因爲用戶個體行爲和精確的時間是不可預知的。

有些信息看起來是足夠隨機的,但實際上未必。有時可以把網絡的輸運情況作爲一種信息源,但我們並不推薦這樣做,因爲這種信息畢竟還是可以由某些外來因素控制的。另一個缺點是使用毫秒精度的時鐘:對於比較高的要求,這種刷新頻率顯得的太慢了。

在討論信息熵收集方法缺點方面,這裏有一個不錯的案例:Netscape公司使用的SSL加密協議是可破解,它並沒有使用真正的RNG。(譯者注:可以在下面的網址找到介紹 http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html)Netscape公司標誌進程和父進程時使用時間和日期,並把它們當作唯一的信息源。在win9x中進程標誌通常都是由一個小於100的值(每增加一個新進程就加一)和win9x第一次啓動時的時間日期作異或運算得到。雖然由哈希函數得到的結果看起來是隨機的,實際上猜測出那個值,計算並且得到正確的結果並不是件難事。如果可利用的信息熵非常有限,那麼輸出結果是不是真的隨機也就不是很重要了(好像有點無奈的意思:P)。


1.2 信息熵的估算

我們不能忽視對收集到的信息熵總數的估算。這一步很重要。否則隨機數產生器輸出結果中信息熵的數量有可能超過輸入的信息熵的數量。根據相應的系統參數,我們可以給各個信息源賦相應的值。比如,對於鍵盤活動,不管我們能夠收集的信息熵總數是多少,我們都可以假設所有鍵盤活動產生的信息熵的大小都是4比特的。如果RNG在文件服務器上,並且把磁盤輸入/輸出作爲信息源,我們可以根據某時刻正在訪問磁盤的用戶數相應的估計信息熵,而不是根據訪問序列。如果基於後者,則可能產生多餘的信息。對信息熵大小的估算不一定要和輸入或輸出的大小一樣。這條準則在今後的計算中能起到安全預防的作用。

對信息熵的估算還有其他的方法。如果信息源超過一定時間間隔後還沒有給我們提供數據,使用統計的方法計算偏差會獲得更好的效果。我們可以從buffer收集大量的數據信息,經過壓縮,得到壓縮比的值。相對於之前輸入的大量數據,統計測試表明,最後一次輸入的數據對於檢驗當前輸入數據整體屬性起不到什麼作用。但是對於RNG來說,則意味着可以把這些只能增加統計概率的輸入數據捨棄。

最好的辦法莫過於多試幾次。在估算信息熵值時用一種方法往往是不夠的。信息源的情況是複雜的,得到的信息熵也是多種多樣的。可是在實際中,對所有的信息熵的大小往往賦同一個值。因此在做這種假設之前,必須仔細的分析一下。多嘗試幾個值是明智的選擇。而最小的值往往是最準確的。


1.3) 信息熵的集合

沒有任何信息源可以說是完美無缺的。確切點說,在計算機上是這樣。這就是爲什麼我們在buffer(信息熵的集合)收集了信息之後還要進行一些處理。收集完畢後,信息熵就被輸入到一個集合裏。這個集合必須和輸入有以下的關係:要知道包含元素的個數,把最後一次輸入合併到之前收集的數據中並保持其一致性。另外,拋開那些收集到的信息熵的屬性不論,集合還要爲這些數據提供一個至少看起來隨機的狀態(相似的輸出在這個集合裏也應該是看起來隨機的)。

對於某個集合狀態,處理集合內容時(這裏指把所有元素合併起來),既不能減少元素,也不能添加元素。如果經過某個合併函數的處理,集合變大了,那麼必須保證這對信息熵的估算是沒有影響的。只有那些負責收集信息熵的函數才能改變信息熵的大小。而且這些收集函數是分開作用的,彼此獨立。

最好的合併函數是哈希算法(譯者注:又稱散列法)。哈希算法能夠接受任意大小的輸入,它的大範圍輸出可以反映信息熵合併的速度,並且這個算法的輸出是不確定的。爲了保護那些合併後的信息熵(譯者注:保證沒有信息丟失),哈希函數輸入的大小不大於輸出的。也就是說,如果哈希函數的輸出是160位的,那麼之前的輸入最多160位。如果哈希函數用於密碼處理上是安全的(事實上的確如此),那麼輸出的信息熵的個數應該和輸入的一樣。但是如果輸出的多於輸入,並不能認爲信息熵集合裏的態一定增加了。

處理大集合有以下幾種途徑:一種方法是把這個集合線性hash(雜化)。使用這種方法,你可能需要一個buffer保存最近的一次輸入。hash從buffer的尾部開始,一次只hash一塊(塊的大小和輸出的大小相同)。每次把當前輸出和前一個塊的hash結果作異或運算,以次保證整個集合只被最近的一次輸入作用,而不會改寫以前的結果。這只是一個例子。不管你選擇什麼樣的處理方式,必須保證這種方式遵守前面所說的各條準則。

另一個處理大集合的方法是“multiple hash(多樣雜化)”,使集合的內容互相影響。一個常見的用途就是用於處理包含“不可操作的信息熵”的集合。一旦這個集合滿了,它就會被hash並用於更新另一個集合。更新的方式可以是更新hash的關係,也可以是直接作異或運算。這樣儘可能多的集合就串聯了起來。爲了避免丟失已有的信息熵,一些集合只有在它們的父集合(更新這些集合的集合)被更新若干次(譯者注:更新次數事先定義好的)後才能被操作。比如,只有當第一個hash集合被更新了8次後,下一個集合才能被更新。只有下一個集合被更新滿了3次,它才能用於hash第三個集合。在這種方法裏,第三個集合包含了經過24次hash的信息熵。這樣保留下來的原始信息熵變少了(受雜化關係的限制)但是這些信息熵的性質卻更好了。因爲第三個集合裏的信息源完全基於24次輸入的信息熵。

向一個集合中輸入信息熵往往稱爲更新或播種。這種信息熵的集合和基於它自身構建的輸出函數實際上是一個PRNG。在收集信息熵的過程中獲得的真正的隨機種子纔是得到RNG的原因。只要輸入了一個好的信息熵,RNG就產生一個無界區域(沒有輸出模式)。這和PRNG正好相反。相對於RNG的無界區域,後者是從一個半確定的點開始,重複以前的所有輸出,並且重複的順序和RNG是一樣的。

信息熵的集合對於防範攻擊者推算RNG以前的輸出和以後的輸出結果至關重要。對RNG的攻擊就是基於三部分:對信息熵集合性質的瞭解,信息熵的輸入和以前的輸出結果。要防止別人瞭解集合當前的狀態,就要對以後的輸出結果做一些處理。因此,集合必須不時地做一些大的變動,刪除一部分或是全部的信息熵。這個過程叫做再播種。再播種,總是(譯者注:用逗號隔開是爲了強調),在輸出之前用一些新的信息熵替換那些已經被刪除的。如果沒有上面的替換,這個集合的安全性就很成問題了。RNG不需要再播種,但是如果沒有這步,就必須不停的添加信息熵的輸入,添加的速度還要高於輸出的。

並不是任何時候都要再播種的。只有當未用過的信息熵積累了足夠多並且佔了集合空間的一大部分時才使用再播種。要注意的是,對集合中信息熵的估算要隨着輸入數據的大小作相應的調整。再播種不能頻繁的使用。是否使用它的唯一根據就是隨機數生成器輸出的比特位數和整個集合的大小。當集合裏95%的信息熵都已經輸出時採用再播種,這是一個比較適當的頻率。這裏我們假設信息熵的輸入是在RNG輸出的空隙間完成的。如果不是這樣,那麼使用再播種的次數有可能應該更多一些。在輸出空隙間輸入的信息熵越少,攻擊者就越容易找到輸出方式的蛛絲馬跡,從而推算出上一次輸出的結果(這樣循環往復,一個鏈式的逆向推算就有可能成功地得到攻擊者想要的東西)。(譯者注:這裏我們並不規定兩次輸出之間只能有一次輸入。相反,輸入的數據應該多於輸出。這樣,可以想象,在集合裏用不到的數據會越來越多。等他們多到一定程度時,如上文的95%,一次性的替換掉。這就是一次的再播種。)

1.4)輸出函數

RNG的輸出函數必須是單向的。單向的意思是輸入的數據經過函數處理可以得到輸出,但是根據輸出的數據無法計算出輸入的原始數據(譯者注:就是不可逆啦,饒舌)。單向的hash函數是一個非常好的選擇。更復雜的方法是把集合中的一部分元素作爲key data(譯者注:不好意思,我一時還想不出什麼好的詞),使用對稱加密算法,對另一部分加密,並且輸出密文。壓縮-解壓縮也是一個效率很高的單向函數。爲了達到目的,我們可以把集合中一部分元素當作PRNG的種子,生成多種輸出(根據PRNG的種子大小而定)。然後把這些輸出統統放進一個雜化函數得到結果。這樣做保證了效率,因爲處理時很多中間態(解壓縮)hash的結果都是一樣的,真正起作用的只是解壓縮前的那個初始態。

RNG每次輸出時,對它信息熵的估算都要隨輸出的大小而減少。這樣做的前提是假設輸出的數據都是由信息熵組成。由於輸出的信息熵仍然保留在集合裏,這些東西現在就成了冗餘信息,也不該再把它們當作信息熵了(在集合裏)。如果集合的大小是512位,且每次輸出信息熵的大小是160位,那麼三次輸出之後,基本上所有的信息熵都被hash了,這個集合也就需要再播種了。

在處理信息熵集合時,有一個幾乎不可能解決的問題:我們沒辦法確定信息熵的哪些位是要輸出的,哪些不是。緩解這個問題最好的辦法是:即便你看到了RNG的輸出結果,我們也根本不讓你知道哪些信息熵被用了幾次。(譯者注:看起來有點掩耳盜鈴,但的確管用。我偷偷地用,用幾次誰也不知道。感覺差不多了,我就再播種了。神不知鬼不覺:P)當一次輸出完成後,集合的態發生變化,重新排序。這樣,在既不添加信息熵也不再播種的情況下,RNG的輸出結果也不會重複。集合的態的重新排序必須通過單向函數完成,而且輸出函數必須滿足前面提到的各條原則。只要處理的過程不違反前面的規定,我們認爲集合裏信息熵的大小在排序前後總是一致的。

1.5)執行

如果執行時不順利的話,我們前面所作的所有努力都是白費。這裏關於執行我們要談三個方面的東西:媒質,硬件/軟件和輸出的使用。

在未加密狀態,儲存媒質和通信媒質各佔了一個風險。下表列出了各種媒質的風險程度。對於風險程度的比例說明是這樣的:

0 - no risk      無風險
1 - low risk     低風險
2 - medium risk  中等風險
3 - high risk    高風險

MEDIA (媒質)                         RISK(風險)
---------------------------------------------------
RAM              <storage>(儲存)         0 *&
Hard Drive       <storage>(儲存)         1 *&
Shared memory    <transfer>(傳輸)        1 *&
Removable disks  <transfer>(傳輸)        2
LAN              <communication>(通訊)   2 &
WAN              <communication>(通訊)   3

任何正確加密的媒質的風險都是0。
*如果儲存媒質是在一臺與網絡相連的計算機上時,風險度還要加1。
&如果可以通過物理途徑到達的話(computer/Lan),風險度也要加1。

所有媒質的最高風險都應該被解釋成執行風險(向脆弱的機制說再見吧:)。高風險在實際中是不可接受的。媒質的風險程度是否可被接受取決於RNG的輸出值(想想這在攻擊者眼中的價值吧)。除非在你的壁櫥裏有許多的萬能鑰匙,否則一本私人日記就足以應付介質風險了(譯者注:這裏可能不太好了解。但就我的經歷,國外用的日記本大多是帶鎖的。國內也有這種日記本,不過好像比較貴:P)。有關工業機密的一定要採用零風險的RNG。雖然什麼樣的風險是可以接受的往往取決於程序員,但是用戶應該清楚地知道自己的選擇。

硬件的RNG需要有監測能力。如果硬件發生了任何的物理修改,RNG就停止輸出。這可以防止外界探測信息熵集合狀態和輸出。同時,外界無法通過頻率,輻射,電壓或者其他由RNG運行時產生的信息監控硬件的運轉。一旦上述的任何一項可被探測,對於集合或輸出來說都是一個危險。所以,所有的RNG硬件都要適當的屏蔽起來。

軟件的執行就微妙的多了。防範逆向工程始終是個大問題,除非可執行文件發出的數字信號是在和操作系統一樣的層次上執行的。否則,任何對逆向工程的防禦措施只能是“緩兵之計”。所以,程序員一定要儘量減少軟件的風險因素(上面的風險係數表對逆向工程依然適用)。

//下面提到的對RNG的應用和調用它的軟件不同
RNG必須注意只有一個program可以得到自己的輸出。數據傳輸的方式一定要是不可觀測的。輸出信息之間的差異由輸出函數決定,但有時輸出能被拷貝到一個臨時的buffer。欺騙RNG在這個buffer中保存數據或者僅僅把數據拷貝到一些易於觀測的地方,這是有可能做到的。一個簡潔的解決辦法就是:用一個程序自身生成一個key把RNG的輸出結果加密。然而,無論你準備得如何充分,對於攻擊者,程序總是有弱點的。程序員加入軟件的防範措施只不過是個路障(並非不可克服的),但是足夠抵擋99.9%的嘗試攻擊的用戶了。對於剩下的0.1%,我們沒什麼辦法了,因爲理論上任何軟件都是可以被破解的。

1.6)分析

我們常從兩個重要的方面來分析RNG:隨機性和安全性。評估一個RNG的隨機性,我們可以分析輸入(信息熵,收集過程)和輸出(輸出函數)的數據。評估安全性,我們要在信息熵的收集過程,集合,合併函數和輸出函數中尋找漏洞,從任何可能的方面尋找那些能讓攻擊者得到過去,當前或以後的輸出數據的漏洞。我們無法保證這些工作是多麼的有效。唯一可以肯定的事是,一旦一個RNG被破解了,它就完了;否則,在此之前,我們所作的都是在投機。

因特網上有很多統計測試可用於檢測數據的隨機性。(譯者注:我以前用過的一種方法是對所有的數據作傅利葉變換。Origin就可以作這件事。當然你也可以直接在一張座標紙上畫出所有的點,看看他們是否均勻,這顯然是最粗糙的辦法:P)大多數這些測試對數據量的要求都很大,把這些數據存在文件裏,然後得到結果(譯者注:從統計角度看,數據量越大,統計結果越接近於真實值。當然,這裏面還有一些其他的要求,比如偏差什麼的,不多說了)。統計分析的結果是一個概率值,用P表示。這是一個介於0和1之間的浮點型數據。作檢測時,可以把數據塊分成各種大小的,常見的是8位到32位。每次測試,P的精度都是不同的。通常我們期望P的值儘量接近0.5,在正中間,而不是偏向0和1的哪一端。但是如果這個值接近0或1,也並不說明這個RNG就是脆弱的。即使是純粹的隨機數據,這種情況也可能發生。但如果根本得不到接近1或0的值,對這個RNG來說總是個缺陷。想想看吧,如果數據是徹徹底底隨機的話,所有的輸出(各個數據統計值P)看起來就都差不多了。這就解釋了爲什麼相似的輸出是可能的。當P的值不盡如人意時,我們就需要另外取樣並進行測試。如果別的樣品得不到符合要求的P值,則這個RNG有可能有比較固定的輸出,自然不能用它。還有一些測試會得到一個整數和一個期望值。整數離期望值越近越好。Maurer Universal Statistics Test就是一個這樣的例子。(譯者注:相關資料在 http://citeseer.nj.nec.com/maurer92universal.html上有很多)

統計測試的問題在於任何優秀的PRNG或哈希函數都能順利地通過檢測。儘管統計測試的結果並不總是有效的(辨別不出優秀的PRNG和哈希函數),但是想想,RNG也只不過是個RNG,我們只要它的輸出能夠不被預測就行了。(呵呵,外國人也講黑貓白貓理論:P)統計的結果都不可靠,RNG的信息熵也是不確定的了。除非信息源能保證工作的很好,否則最好還是對原始的信息熵用同一種測試比較好。這樣做隨機性有足夠的保障。然而,當你打算這樣做的時候,一個大麻煩正橫在你的面前。信息熵收集的步調通常都非常緩慢以至於收集到那些數據要花很長很長的極端枯燥的時間去等待,在某些情況下,這是不值得的。如何避免這種情況需要你仔細觀察信息源的情況作出判斷,而不是輕易的相信那些統計測試,這些測試不能保證什麼,只是個參考(見1.1)。

評價一個RNG的安全性是一個複雜的工作,方法有無限多,結果卻只有一個:破解。對於RNG來說,破解成敗的可能性探討起來很有趣。無論之前這個RNG抵擋住了多少次的破解進攻,總是會有新的攻擊跑出來(一次的失敗就已經足夠了)。RNG的方方面面都要仔細研究,從信息熵的收集到輸出結果的傳輸。每個組件都要單獨測試,然後再組合起來調試。測試包括信息熵收集被hacked或控制的機率,還有對合並函數和輸出函數的密碼學分析。很多破綻都是在實驗室條件下發現的。這叫做“學術破綻(academic breaks)”。讓他們現出原形的條件通常都很特殊(一般情況下基本上是不可能的)(譯者注:用我的話來說,就是“讓他們現出原形的條件通常都很變態”:P)。對這些破綻的尋找是一個大課題,已經超出了本文的範圍。成功的破解通常需要富有經驗的密碼專家的數月(經常是數年)時間的艱苦努力。最好的做法就是從開始設計RNG到完工始終把安全性的問題牢記腦中。

即使測試中已經極盡數學和密碼分析學之能事,不過不要大意,科學的發展帶來的新東西可能會給你的RNG帶來新的破解方法。舉個例子,Tempest Scanning可以用來監控擊鍵和鼠標移動。甚至對白噪聲(譯者注:見前文解釋)的分析也能帶來新發現。那些學者和專業人士通常希望在破壞發生前盡他們的能力找出這些破綻。對於未知的攻擊,我們做不了什麼。但是開發者希望能找到快速有效的修復方法並從中汲取經驗。幸運的是,這種攻擊鳳毛麟角,但是隨着研究的深入,情況總是在變的。

我們只討論在第二部分出現的那幾種RNG,因爲它們都經過了測試並通過了隨機性分析。

----| 2 對幾種RNG的討論


2.1) NoiseSpunge的設計
源代碼:活活,我已經給出了。


2.1.0)概述

NoiseSpunge是專門用來生成256位隨機key的工具。它的加密十分堅固。爲完成一次輸出(256位),收集信息熵時需要用戶移動鼠標幾秒鐘。其中的過程很複雜,而且運算量很大。當NoiseSpunge被選用於密碼系統時,往往需要考慮一些特殊的實際情況。使用NoiseSpunge的缺點在於如果需要頻繁的輸出大量隨機數據,它就顯得有些笨拙了。因爲用戶要不停的移動鼠標,做出反應,而且它佔用的CPU資源也較多。


2.1.1)NoiseSpunge對信息熵的收集

先給PRNG一個初始值爲0的種子,用輸出的值計算一個時間段的長度。每當時間一到,就檢查鼠標當前的位置。時間是由計算機的高頻時鐘控制的。時間數據的最少4位有效數據和鼠標x,y軸座標的至少4位有效數據作異或運算。然後,一個新的時間段又被給出。如此反覆,直至32位信息熵都被收集完畢並且輸出。32位數據輸入到一個信息熵buffer中,同時用於更新設置時間的那個PRNG。這個過程被不斷的重複。如果鼠標沒有移動,新的時間段仍然會不斷生成,上面的過程也不斷循環直到發現鼠標移動。也有一種函數允許程序員一次性輸入32位信息熵。如果信息源是硬件或在特定系統上能保證是安全的,這種函數還是很合適的。對於其他RNG的輸出方法,在這種情況下,不是顯得多餘(如果管用的話),就與沒什麼用處(如果不管用)。


2.1.2)NoiseSpunge對信息熵的估算

信息熵的估算沒什麼複雜的地方。最麻煩的事就是一次次的輸入。每次鼠標移動只能捕獲4比特的數據。我們不需要再估算什麼,因爲它們只能產生4比特或更大一點的結果。有時候,一些輔助函數允許程序員添加一些自己的信息熵,這就需要程序員們保證他們的信息熵絕對是好用的;對這些輸入的信息熵估算的工作,就只能靠他們手動完成了。


2.1.3)NoiseSpunge的信息熵集合

集合裏的態包含762位(譯者注:是不是768位。加法做錯了?),包括一個256位的種子,一個256位初級雜化和一個256位的二級雜化。256位的HAVAL被用作雜化函數。當一個32位的信息塊被添加時,它是被追加到一個256位的buffer中去的。一旦buffer滿了,就用初次雜化更新它。種子一般是和初次雜化的結果作異或運算,除非這是第8次的初級再播種。(譯者注:看看前面的例子,8次,3次,所以24次)再播種時,把初級雜化的輸出當作輸入數據作二級雜化。雜化結果改變了(見下面)並且替換種子。種子的改變是一個壓縮-解壓縮過程。32位的種子用於PRNG的隨機種子,通常輸出兩個32位的數據。所有PRNG512位的輸出都被雜化而且替換了集合的種子。每次初級再播種後,一個計數器就加1,直到8。這個計數器代表有多少個256位的信息熵已經加入了集合。計數器的作用在於粗略的判斷什麼時候不再需要添加信息熵,並暫停收集信息熵的線程(直到RNG輸出)。

2.1.4)NoiseSpunge的輸出函數

RNG的輸出有兩種方法:安全的和強制的。安全輸出先判斷計數器不爲零,並遞減,然後再輸出。強制輸出則不管計數器。輸出時,種子被拷貝到一個暫時的buffer裏,然後重排序。新種子被用於初始化Rijndael(塊對稱加密)。這個暫時的buffer用Rijndael加密並經過壓縮-解壓縮處理(和處理種子一樣)。這樣重複N次(N由程序員決定)後buffer輸出數據。


2.1.5)NoiseSpunge分析

[1] 由於對鼠標移動過分依賴,一旦鼠標有一段時間不動,集合裏的信息熵就會消耗殆盡。防治這種情況的辦法是用一個計數器,當信息熵很少的時候就停止輸出。

[2] 如果程序員強制的輸入一些質量差的信息熵,這對RNG的內部狀態會有損害。

[3] 沒有高精度的計時器,NoiseSpunge就不會爲系統工作。

[4] 即便集合的態是762(768?)位的,在任何態,信息熵的最大值都是256位。(其他的比特位其迷惑作用,用來掩護種子以防逆向工程的)。這樣就決定了這個RNG只適用於一種情況:當隨機數據的需求量較小時。


2.2)Intel RNG 的設計
信息來源:Intel 隨機數發生器白皮書 *1 (Intel Random Number Generator White Paper *1)


2.2.0)Intel RNG 概述

Intel RNG使用的系統很多。它可以爲任何軟件提供超大量的優質隨機數。它的平均“生產量”是75kb/s(比特)。Intel安全驅動在中間設備(CDSA, RSA-BSAFE, 和 Microsoft CryptoAPI)間架起了一座橋樑。這些中間設備負責把產生的隨機數分發給提出請求的應用程序和硬件。硬件部分是Intel的810芯片,在將來的8xx芯片組裏會集成到82802 Firmware Hub Device中。

{警告:下面是我個人的觀點(譯者注:當然是作者原話了),就當時正餐之餘的一碟小菜吧。}
Intel把它的RNG標榜成TRNG(True Random Number Generator 真正的隨機數發生器),但是在白皮書的後面卻繼續稱之爲RNG。從技術上說,把它跟其他優秀的RNG之間並沒有什麼根本的區別。這是個騙人的噱頭,和RNG生成隨機數的能力根本無關(RNG==TRNG & TRNG==RNG)。至於你看到的法人擔保:“Intel RNG的輸出得到密碼研究有限公司(CRI)的承認,並且通過了聯邦信息處理中心(FIPS)關於隨機數的第三級別統計測試(FIPS 140-1)。”對這個產品我比較放心,因爲有個公司(CRI)專門對它做過分析並且支持這個產品。這種情況是不多見的。另一方面,FIPS140-1只是另一個騙人的東西。讀完了它,你會發現它和我們想知道的RNG的性能根本沒有任何關係。但是,嗨,管它呢!微軟覺得這個東西不錯,把它用於他們的“高質量安全產品”系列(their family of _high_quality_and_security_ products)中,所以應該是很不錯了。言歸正傳,拋開浮誇的法人擔保不說,這個產品的確設計得很好。它沒有其他很多RNG都範那些的錯誤,就像Netscape的那個。我認爲這是在正確方向上邁出的一步。Joe( Timmy的表弟),還有Timmy最好的朋友的朋友,他們沒有像Netscape那樣閉門造車,而是合作起來爲每個人提供了一個好的解決方案。


2.2.1)Intel RNG的信息熵的收集

Intel的隨機數發生器將要被整合到PC機的主板裏,用到兩個電阻和兩個振盪器(一個比較慢,另一個比較快)。兩個電阻之間的電壓差被放大用來對熱噪聲採樣。噪聲源被用來調節慢的時鐘。這個時鐘有多種調節選擇。它的用途是給另一個較快的時鐘設置測量時間段的。當這個時間段被觸發,較快時鐘的頻率就被輸入一個Intel稱作von Neumann corrector(尚未獲得專利)的程序。這個過程像被過濾一般。這個程序爲了補償較快時鐘的誤差(這個時鐘喜歡停滯在固定的比特狀態),它通過比較一對對的比特位,只輸出一個或不輸出([1,0]=0; [0,1]=1; [0,0]或[1,1]=不輸出)。這個corrector的輸出是32位的數據塊,並且這些塊是送給Intel安全驅動的。

2.2.2)Intel RNG信息熵的估算

這裏對收集過程不做什麼估算。因爲信息源是基於硬件的,如果工作環境的溫度和正常值沒有偏離得過分,或者電源被斷開(這種情況下信息熵的收集停止了),這個信息源是不可人爲控制的。除了以上的特殊情況,所有的信息熵的收集過程都是一樣的,而且我們認爲他們的性質沒有差別。


2.2.3)Intel RNG的信息熵集合

Intel安全驅動負責看護合併RNG輸出的工作。集合由512位的SHA-1(一種安全雜化算法)雜化關係組成。這個雜化關係分爲兩個狀態。第一狀態的80位的雜化生成後,在後面追加上32位的信息熵(從硬件得到)。再由第一狀態的前160位生成第二狀態。當另一個32位的信息熵產生後,把第二狀態標誌成第一狀態,然後重複前面的過程。


2.2.4)Intel RNG的輸出函數

第一狀態的80位雜化的最後16位輸出到中間設備。 Intel安全驅動保證每次輸出只有一個目的地,不會分發到多個地方。如果需要,發出申請隨機數請求的程序要對輸出作些額外的處理。


2.2.5)Intel RNG 分析

[1] 需要執行von Neumann修正程序,因爲RNG和循環次序有密切關係。一個攻擊者可能通過估算RNG的“生產量”計算出什麼時候1s或0s是不成比例的輸出。但是這還不足以形成什麼威脅。

[2] 基於協議的中間設備的使用可能會導致一些漏洞。在開始正式使用一個公司的中間設備之前,你可能需要花幾個月的工夫看看使用中會不會有一些很短暫的停頓。


2.3)Linux的 /dev/random 的設計

2.3.0)/dev/random 概述

Linux把/dev/random作爲一個接口提供給那些需要優質隨機數的應用程序。它提供了一個大容量的信息熵集合(512比特)以滿足操作系統和各種軟件。當信息熵的性質不太重要時,另一個設備/dev/urandom可以用作PRNG,提供僞隨機數,免得耗費/dev/random裏的信息熵集合。


2.3.1)/dev/random 的信息熵收集

內核提供的外部函數負責觸發向集合裏添加信息熵這個事件。觸發事件可以是按鍵,鼠標移動,也可以是IRQ中斷。對於每次觸發,32位的高頻時間被複制,同時生成另一個32位的數據。後一個32位的數據基於某種觸發的類型(鼠標狀態,鍵盤的掃描代碼(譯者注:the data sent by the keyboard is called a scancode, 我只會用英文解釋,中文該怎麼說?),或IRQ代號)。


2.3.2)/dev/random 信息熵的估算

信息熵的估算基於三個Δ(譯者注:就是那個讀作delta的東西啦)。Δ1表示自從上一次觸發後所經過的時間。Δ2表示現在的Δ1和以前的Δ1的差值(Δ2=Δ1[this time]-Δ1[last time])。Δ3表示現在的Δ2和以前的Δ2的差值(Δ3=Δ2[this time]-Δ2[last time])。Δ的值取Δ1,Δ2和Δ3中的最小值(Δ=min(Δ1,Δ2,Δ3))。這樣我們捨棄了Δ中不重要的部分,剩下來的12位數據用來增大信息熵計數器的值。


2.3.3)/dev/random的信息熵集合

這個RNG使用一個4096比特的集合。在輸入之前,做一個標記表示當前在集合裏的位置要減去2個32位的數據。如果當前位置爲0,這個位置就退回到倒數第二個32比特的地方。信息熵總是同時增加兩個32位的:x和y。變量j表示有多少位是信息熵要循環左移的。在信息熵增加前,j要減去14(當集合當前位置是0時,j減7)。信息熵根據j的值改變。根據當前的位置,y和集合裏其他5個固定的部分作異或運算(接下來的位置就是從當前位置開始封裝:103,76,51,25,1)(對於4096位的集合),同時x分別和後面的字作異或運算。x右移3位,再和一個1x7的表格裏的中一個常數作異或運算(0,
0x3b6e20c8, 0x76dc4190, 0x4db26158, 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0,0xa00ae278)。具體和哪一個異或則由x和7的與運算結果(3比特)決定。然後把x和y作異或運算,把結果跳過一個字追加到集合裏。y右移3位,和前面x一樣和一個常數異或,並把結果拷貝到剛纔跳過的那個字裏。(譯者注:這裏說的“字”指得都是32位的。我是不是該用“雙字”這個詞?)集合保存這個位置(以前的位置減2,可能在集合的最後)。


2.3.4)/dev/random的輸出函數

當RNG需要輸出時,計時器的值和那些比特位被當作信息熵輸入到集合裏。然後集合和SHA-1雜化,雜化結果的前兩個字作爲信息熵輸入到集合裏。這個步驟重複了8次,但是每次雜化結果的第三個和第四個字也被輸入到集合裏。最後的雜化結果的前半段和後半段再作異或運算,這個結果纔是輸出結果(譯者注:快暈了~~基本上整個集合都換了吧)。結果的大小不是請求的大小就是20比特(雜化結果的一半),取這兩者中較小的一個。


2.3.5)Linux' /dev/random分析

[1] 在網絡環境下對一些IRQ的控制和預測是有可能實現的。

[2] 在增加的信息熵的低16位裏會有一些冗餘信息。舉個例子,當按鍵產生一個32位的變量,其中16位來自於高精度的計時器,低16位則是按鍵的產生的0-255(256以上的是用於設計中斷的)。這樣每次按鍵都會產生8比特的冗餘信息。

[3] 上一次信息熵數據塊輸入後的時間通常和整個信息熵的性質沒什麼關係,除非這個時間非常短暫。這和移動文件時持續訪問磁盤不一樣,信息熵序列不是連續的。

[4] 輸出時,合併機制重新輸入雜化後的信息熵,這些數據的性質可能好,也可能不好。這些重新輸入的數據被添加到信息熵的計算裏,而實際上不該這麼做。它們已經被計算過一次了。輸出後,512位的信息熵作爲冗餘信息被添加了進來。如果估算的沒錯的話,輸出8次後,集合裏有4096比特未知性質的信息熵(整個集合裏)。在這種情況下,如果沒有來自基於用戶反應的輸入的話,RNG就成了PRNG。


2.4)Yarrow的設計
信息來源:Yarrow源碼和白皮書 *3,*4。


2.4.0)Yarrow概述

Yarrow的設計者是Bruce Schneier。他是《應用密碼學》的作者和Blowfish塊狀密碼及AES 入圍決賽的 Twofish 的設計者。Yarrow是Schneier講解如何設計一個好的RNG時用的例子,並有一篇詳細的文章介紹它的內在工作機制和分析(見信息來源中的第二篇)。這個產品是長期研究的成果,並對RNG的安全性提出了一些標準。列在這裏是爲了讓大家比較比較市場上流行的RNG和一個經驗豐富的教授設計出來的RNG有什麼不同。


2.4.1)Yarrow的信息熵收集

用系統鉤子等待鍵盤或鼠標的事件。如果一個鍵被按下,從上一次按鍵到現在經過的時間被添加到一個數組裏。鼠標按鍵的情況和這個一樣。如果鼠標移動了,x軸和y軸的座標也會被添加到一個標誌鼠標移動的數組中。一旦這個數組滿了,就會移交給信息熵的估算函數。


2.4.2)Yarrow對信息熵的估算

信息熵的估算函數處理的數據是由程序員通過對信息源的理解自己選出的。比如,你可以定義一次鼠標移動的信息熵是4比特,每次按鍵的鍵盤延遲是8比特。另一種測量方法是採用一個小的壓縮算法並測出壓縮後的大小。第三種方法是直接取信息熵採樣得到的數據大小的一半。這三種測量方法得到的最小值作爲信息熵大小估算的結果。


2.4.3)Yarrow的信息熵集合

信息熵被輸入到一個“快”集合裏(由SHA-1算法得到),信息熵的估算被這個集合更新。一旦“快”集合收集滿了100比特的信息熵,雜化輸出被送入一個“慢”集合,信息熵的估算也被同時更新。當這個“慢”集合收集滿了160比特的信息熵,它的雜化輸出纔是最後我們用到的key。


2.4.4)Yarrow的輸出函數

輸出時,用那個key(由“慢”集合生成的)加密一個計數器(它的比特數是由程序員選定的)並且輸出密文;計數器隨之加1。10次輸出後,RNG對key再播種,用另一個輸出(強制的)替換它。那個“慢”集合收集滿160比特數據或10次輸出完成都會引起key的再播種。


2.4.5)Yarrow分析

[1] 鼠標自身的運動是很繁雜的。當操作系統傳遞鼠標位置的信息時,鼠標產生的新位移是非常有限的。標誌鼠標移動的數據中很多位都不會改變,這對RNG中信息熵的估算會產生一些不良影響。(譯者注:想想文章開頭舉到過的那個例子)

[2] 即使集合內部的態是320+n+k比特那麼長,任何態上可容納的信息熵最多爲160比特。“Yarrow-160,我們現在的產品,從安全角度考慮,它的信息熵集合大小規定不超過160比特。”*4



----|3)NoiseSpunge源代碼

下面的源代碼只是一個簡單的例子。你可以對它作任何事;甚至是*@#@!#!#(譯者注:原文這裏寫得比較xxx,但願是因爲我的水平太次:P)......沒關係的。這個源代碼是不完整的,因爲我已經省略了包含Haval, Rijndael 和 PRNG的1200行代碼。Haval 和 Rijndael的源代碼很好找。另外,任何的PRNG都可以用在這裏,只要你注意它的輸入和輸出是32比特的並且範圍是2^32(4294967296)就行了。我把它分成了三部分:信息熵的收集,信息熵集合,還有輸出函數。

[信息熵的收集]

分配給這個循環的線程必需和應用程序的主線程互相獨立。考慮到移植性,我寫了一個啞元函數以供替換:

int64 CounterFreq; //高精度計數器的頻率 /秒
int64 QueryCounter; //高精度計數器的當前值
Delay(int ms); // 一毫秒精度的延遲
int GetMouseX; //當前鼠標x軸座標
int GetMouseY; //當前鼠標y軸 座標

#define MOUSE_INTERVAL 10

{
Prng_CTX PCtx;
int x,y;
unsigned long Block;
unsigned long BitsGathered;
int65 Interval,Frequency,ThisTime,LastTime;

unsigned long BitsGathered=0;
bool Idled=false;
Frequency=CounterFreq;
bool Terminated=false; //循環結束時設爲真
do
{
    if (Idled==false)
    {
       Delay(MOUSE_INTERVAL);
       Idled=true;
    }
    ThisTime=QueryCounter;
    if ((ThisTime-LastTime)>Interval)
    {
       if ((x!=GetMouseX)&&(y!=GetMouseY)
       {
          x=mouse.cursorpos.x;
          y=mouse.cursorpos.y;
          Block|=((x^y^ThisTime)& 15)<<BitsGathered;
          BitsGathered+=4;
          if (BitsGathered==32)
          {
             PrngInit(&PCtx,Block);
             AddEntropy(Block); //這個函數在下面有定義
             Block=0;
             BitsGathered=0;
          }
          Interval=((((Prng(@PCtx)%MOUSE_INTERVAL)>>2)+MOUSE_INTERVAL)
             * Frequency)/1000;
       }
       LastTime=QueryCounter;
       Idled=false;
    }
} while (Terminated==false);
}

[信息熵集合]

#define SEED_SIZE 8
#define PRIMARY_RESEED 8
#define SECONDARY_RESEED 8

//參數
#define MAX_KEY_RESERVE 8
#define KEY_BUILD_ROUNDS 16

typedef unsigned long Key256[SEED_SIZE];

Key256 Seed;
Key256 EntropyBuffer;
Haval_CTX PrimaryPool;
Haval_CTX SecondaryPool;
unsigned char PrimaryReseedCount;
unsigned char EntropyCount;
unsigned char KeyReserve;

//函數
void NoiseSpungeInit
{
HavalInit(&PrimaryPool);
HavalInit(&SecondaryPool);
for (int i=0;i<8;i++) Seed[i]=0;
EntropyCount=0;
PrimaryReseedCount=0;
KeyReserve=0;
}

void PermuteSeed
{
Key256 TempBuffer[2];
Prng_CTX PCtx;
Haval_CTX HCtx;

for (int i=0;i<SEED_SIZE;i++) //解壓縮
{
    PrngInit(&PCtx,Seed[i]);
    TempBuffer[0][i]=Prng(&PCtx);
    TempBuffer[1][i]=Prng(&PCtx);
}

HavalInit(&HCtx);
HavalUpdate(&HCtx,&TempBuffer,64); //壓縮
HavalOutput(&HCtx,&Seed);
}

void PrimaryReseed
{
Key256 TempSeed;
HavalUpdate(&PrimaryPool,&EntropyBuffer,32);

if (PrimaryReseedCount<SECONDARY_RESEED)
{
    HavalOutput(&PrimaryPool,&TempSeed);
    for (int i=0;i<SEED_SIZE;i++) Seed[i]^=TempSeed[i];
    PrimaryReseedCount++;
} else SecondaryReseed;

for (int i=0;i<SEED_SIZE;i++) EntropyBuffer[i]=0;
if (KeyReserve<MAX_KEY_RESERVE) KeyReserve++;
EntropyCount=0;
}

void SecondaryReseed
{
HavalOutput(&PrimaryPool,&Seed);
HavalUpdate(&SecondaryPool,&Seed,32);
HavalOutput(&SecondaryPool,&Seed);
PermuteSeed;
HavalInit(&PrimaryPool);
PrimaryReseedCount=0;
}

void AddEntropy(unsigned long Block)
{
EntropyBuffer[EntropyCount++]=Block;
if (EntropyCount==PRIMARY_RESEED) PrimaryReseed;
}

[OUTPUT FUNCTIONS]

int SafeGetKey(Key256 *Key)
{
Key256 TempSeed;
Key256 TempBuffer[2];
Rijndael_CTX RCtx;
Prng_CTX PCtx;
Haval_CTX HCtx;

if (KeyReserve==0) Return 0;

for (int i=0;i<SEED_SIZE;i++) TempSeed[i]=Seed[i];
PermuteSeed;
RijndaelInit(&RCtx,&Seed);
for (int i=0;i<KEY_BUILD_ROUNDS;i++)
{
    RijndaelEncrypt(&RCtx,&TempSeed[0]); //加密
    RijndaelEncrypt(&RCtx,&TempSeed[4]);
    for (int j=0;j<SEED_SIZE;j++) //解壓縮
    {
       PrngInit(&pctx,TempSeed[j]);
       TempBuffer[0,j]=Prng(&PCtx);
       TempBuffer[1,j]=Prng(&PCtx);
    }
    HavalInit(&HCtx);
    HavalUpdate(&HCtx,&TempBuffer,64);
    HavalOutput(&HCtx,&TempSeed);
}
for (int i=0;i<SEED_SIZE;i++) Key[i]=TempSeed[i];
if (KeyReserve>0) KeyReserve--;
Return 1;
}

void ForcedGetKey(Key256 *Key)
{
Key256 TempSeed;
Key256 TempBuffer[2];
Rijndael_CTX RCtx;
Prng_CTX PCtx;
Haval_CTX HCtx;

for (int i=0;i<SEED_SIZE;i++) TempSeed[i]=Seed[i];
PermuteSeed;
RijndaelInit(&RCtx,&Seed);
for (int i=0;i<KEY_BUILD_ROUNDS;i++)
{
    RijndaelEncrypt(&RCtx,&TempSeed[0]); //加密
    RijndaelEncrypt(&RCtx,&TempSeed[4]);
    for (int j=0;j<SEED_SIZE;j++) //解壓縮
    {
       PrngInit(&pctx,TempSeed[j]);
       TempBuffer[0,j]=Prng(&PCtx);
       TempBuffer[1,j]=Prng(&PCtx);
    }
    HavalInit(&HCtx);
    HavalUpdate(&HCtx,&TempBuffer,64);
    HavalOutput(&HCtx,&TempSeed);
}
for (int i=0;i<SEED_SIZE;i++) Key[i]=TempSeed[i];
if (KeyReserve>0) KeyReserve--;
}



----| 4) 參考文獻

*1 Intel 隨機數發生器白皮書
  http://developer.intel.com/design/security/rng/CRIwp.htm

*2 /dev/random 源代碼
  http://www.openpgp.net/random/

*3 Yarrow 源代碼
  http://www.counterpane.com/Yarrow0.8.71.zip

*4 Yarrow-160:關於Yarrow密碼僞隨機數發生器的設計和分析筆記
  http://www.counterpane.com/yarrow-notes.html



----| 5)最後的話

首先,感謝您讀完了這篇文章。我知道,只是一篇偏於理論性的文章,對它感興趣的人也許不會太多。一些心得我都已經夾雜在行文中了,希望對大家理解有些幫助。如果您對這篇文章的理解有些疑問或困難,我先道歉了。我的翻譯水平和對RNG知識掌握的有限也許反而會增加您的理解負擔。我推薦您看一看原文,雖然裏面有不少的拼寫錯誤......

認真讀完這篇文章的人可能都會有一個感覺:設計一個好的RNG,從頭至尾都不能有一點點的鬆懈。任何馬虎都有可能給攻擊者以機會,使自己蒙受損失。關於技術方面的東西,我也給出了一些參考資料,其中大部分是論文。這個課題無論從廣度還是深度都是很有挖掘潛力的。如果您讀完了這篇文章,對裏面的技術細節還沒有充分了解(我讀了好幾遍纔有了點頭緒),不要緊,首先記住一點:安全第一。

裏面談論技術時都比較繁雜,儘量用自己的理解看問題,多想想,不要被牽着走,常常把現在所說的問題和前面提到的例子結合起來看。這樣也許對理解本文有些好處。

最後,謹以此文獻給cherry。雖然我知道你對這個不感興趣,但你至少沒有反對我做這件事:)

ori published:http://www.xfocus.net/articles/200209/451.html

我開始找到一篇被刪除了翻譯信息以及一些參考信息,對比看上一篇.............

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章