1. 程式人生 > >隨機數產生器(random number generator)

隨機數產生器(random number generator)

隨機數產生器

隨機數產生器一般分為真正的隨機和偽隨機(pseudorandom number generator (PRNG))。真正的隨機則是不可預測的;偽隨機則依靠隨機數種子和隨機演算法,是可能被預測的。很多東西被黑客攻破與此有關。比如非常著名的美國所謂的標準隨機數產生器的後門事件,簡單來講,就是“美國所謂的標準隨機數產生器裡面居然存在後門,通過某個常數,可以預測未來的隨機數,從而攻破任何用此隨機數產生器的加密裝置。

Dual_EC_DRBG是美國國家標準技術研究院所制定的4個隨機數標準演算法之一。該演算法使用了一個常數Q,如果該常數經過特殊選擇,並且用來選擇常數所使用的資料被儲存,那麼在知道該演算法生成隨機數前32個位元組的情況下,可以預測未來所有生成的隨機數。因此,如果該演算法被植入後門,攻擊者就可獲知利用Dual_EC_DRBG演算法生成的加解密金鑰,從而使得保護的祕密洩露。2014年4月21日,美國國家標準技術研究院所宣佈從隨機數標準演算法中刪除Dual_EC_DRBG。

只依靠計算機程式,不依賴硬體和外部噪音能否產生真正的隨機?
個人分析:
不可能。原因如下:種子是可選擇的,產生序列的演算法程式碼也是固定的,那麼這個序列則是可以預測的,即使引入“時間”這種動態的變數,也是一個可以獲取和預測的變數,那麼最終的序列仍然可能被預測。這就是為何很多利用偽隨機數進行加密而比較容易被破解的原因之一。綜合而言,採用固定變數產生的資訊是可能被預測的,這是我個人的理解。 當然,不能用我們大部分人攻不破而證明是“不可被預測”,也許黑客可以。D:)

我們舉個例子:

    public static void main(String args[])
    {
        Random rnd = new
Random(10); for(int i=0;i<10;i++) System.out.println(rnd.nextInt()); } 執行兩次的結果: -1157793070 1913984760 1107254586 1773446580 254270492 -1408064384 1048475594 1581279777 -778209333 1532292428 -1157793070 1913984760 1107254586 1773446580 254270492 -1408064384 1048475594 1581279777 -778209333
1532292428

很容易看出,java的Random類的nextInt方法,只要固定了seed,連續兩次的執行序列是一樣的,這顯然不能算作真正的隨機,採用這種方法產生的序列,幾乎100%可預測,而且極其容易。如果稍微變化一下seedseed稍微設定的複雜一些,這種序列的變化只是變得複雜,但仍然是可能被預測的。

當然,大部分程式和語言中的隨機數(比如Java, C 中的,MATLAB 中的),是偽隨機。是由可確定的函式(常用線性同餘),

Xn+1=(aXn+c)modm

通過一個種子(常用時鐘),產生的偽隨機數。這意味著:如果知道了種子,或者已經產生的隨機數,都可能獲得接下來隨機數序列的資訊(可預測性)。
那麼能否產生真的隨機呢?
可以。軟體的方法有缺陷,都是固化的變數和資訊(至少是可知的),引入系統外的變數(把軟體,程式碼,演算法想象成一個封閉的系統)才可能產生真的隨機性。
一個典型的例子就是 UNIX 核心中的隨機數發生器(/dev/random),它在理論上能產生真隨機。即這個隨機數的生成,獨立於生成函式,這時我們說這個產生器是非確定的。具體來講,UNIX 維護了一個熵池,不斷收集非確定性的裝置事件,即機器執行環境中產生的硬體噪音來作為種子。比如說:時鐘,IO 請求的響應時間,特定硬體中斷的時間間隔,鍵盤敲擊速度,滑鼠位置變化,甚至周圍的電磁波等等……直觀地說,你每按一次鍵盤,動一下滑鼠,鄰居家 wifi 訊號強度變化,磁碟寫入速度,等等訊號,都可能被用來生成隨機數。
我們來分析一下linux裡的c程式碼簡略看一下:

==============================
The current exported interfaces for gathering environmental noise from the devices are:

* void add_device_randomness(const void *buf, unsigned int size);

* void add_input_randomness(unsigned int type, unsigned int           code, unsigned int value);

* void add_interrupt_randomness(int irq, int irq_flags);

* void add_disk_randomness(struct gendisk *disk);

add_device_randomness() is for adding data to the random pool that is likely to differ between two devices (or possibly even per boot). This would be things like ***MAC*** addresses or serial numbers, or the read-out of the RTC. This does *not* add any actual entropy to the pool, but it initializes the pool to different values for devices that might otherwise be identical and have very little entropy available to them (particularly common in the embedded world).

add_input_randomness() uses the input layer interrupt timing, as well as the event type information from the hardware.

add_interrupt_randomness() uses the interrupt timing as random inputs to the entropy pool. Using the cycle counters and the irq source as inputs, it feeds the randomness roughly once a second.

add_disk_randomness() uses what amounts to the seek time of block layer request events, on a per-disk_devt basis, as input to the entropy pool. Note that high-speed solid state drives with very low seek times do not make for good sources of entropy, as their seek times are usually fairly consistent. All of these routines try to estimate how many bits of randomness a particular randomness source.  They do this by keeping track of the first and second order deltas of the event timings.

個人以為,通過依賴於硬體的變數產生的資訊在結合優秀的軟體演算法,理論上是可以產生真隨機的。

偽隨機

當然,我們工程中,大部分用的都是偽隨機,其實也沒必要用真隨機。比如:中獎的隨機數產生器,沒必要非要追求真隨機。本來就是”模擬“。
另外,隨機數發生器對區域性搜尋的影響是存在的,可以參考各種論文。比如:

什麼是好的偽隨機數發生器

主要是看:隨機分佈的優劣
主要看四個方面:
1. 相同序列的概率非常低。
2. 符合統計學的平均性,比如所有數字出現概率應該相同,卡方檢驗應該能通過,超長遊程長度概略應該非常小,自相關應該只有一個尖峰,任何長度的同一數字之後別的數字出現概率應該仍然是相等的等等。
3. 不應該能夠從一段序列猜測出隨機數發生器的工作狀態或者下一個隨機數。
4. 不應該從隨機數發生器的狀態能猜測出隨機數發生器以前的工作狀態。

參考:

我們一般用的隨機數發生器至少要符合1和2,而用於加密等應用的隨機數發生器應該符合3和4。

引用文獻