討厭的人類居然讓我們擲骰子,這實在太難了!
周末的深夜,Linux老大發布了緊急會議通知,召集CPU、內存、硬盤等所有硬件,以及git、 vim、瀏覽器、c、 Java等所有軟件參會。
老大對深夜打擾大家深表歉意,表示春節快來了,到時候一定讓大家好好休息,然后就進入中心議題:人類要求我們學會“擲骰子”,該怎么辦?
內存表示不解:為啥?想讓我們賭錢玩嗎?我們這兒可沒有骰子!
Linux老大:其實不是真正的擲骰子,是生成隨機數,隨機數在我們計算機里用途極為廣泛,生成密鑰,進行通信,生成鹽(salt)...... 不可能指望人去手工操作。
vim笑道:生成隨機數? 這太簡單了,讓新手退出我vim就行了。
Linux老大:為啥?
vim: 哈哈,因為新手不知道怎么才能退出vim,就會瞎胡亂按一通, 非常隨機,這不就形成隨機數了嗎?!
CPU阿甘:這笑話夠冷的!
Linux老大:口頭警告vim一次,嚴肅點兒, 生成隨機數是非常重要的事情,人類要求:
1. 要雜亂無章
2. 不能預測,不能根據已經生成的隨機數,推測出下一個隨機數是啥
3. 不能重現, 無法重現和某一隨機數列完全相同的數列
聽到此處,大家都吸了一口冷氣,這要求夠高的!人類通過擲骰子可以達到這個要求,但是計算機里都是確定的算法和程序,這該怎么辦?
C老頭兒說:我提一個方案,我聽說人類有個算法,叫做什么線性同余算法,似乎可以生成隨機數。
C老頭兒寫下了一個公式:
內存想起和CPU阿甘折騰過的遞歸函數調用,說到:“看到遞歸我就頭暈。”
C老頭說:這個算法很簡單,A, C, M 都是精心挑選的整數,使用者在生成隨機數之前,先選一個種子(seed),比如說用當前的時間戳當作種子,我們用簡單的數字做個實驗, 讓A = 11 , C =5, M = 13, seed = 15
“看看,是不是挺隨機的!” C老頭兒挺得意。
Java 說:“這個算法很簡單嘛,效果也不錯,我也實現一下,放到我的java.util.Random當中吧。”
C老頭說:“我就放到我的srand函數和rand函數里。srand用來設定‘種子’,rand用來獲取下一個隨機數。”
- srand((unsigned) time(&t));
- // 輸出5個隨機數
- for(int i=1 ;i<=5; i++){
- printf("%d\n", rand());
- }
Java說:“看看,還是面向對象好吧,我的Random類封裝了內部狀態,用戶只需要在創建Random對象的時候把種子傳進去(不傳也行,我自己默認給它設置一個),然后就nextInt()方法就可以了。多簡單!”
- Random r = new Random();
- for(int i=1; i<=5; i++){
- System.out.println(r.nextInt());
- }
C老頭兒正要反擊自以為是的Java, CPU阿甘又發言了:“不對啊,你這個隨機數不符合我們老大的要求啊,這不是真正的隨機數,這是偽隨機數!”
C老頭兒馬上把注意力轉移到阿甘身上:“憑什么說這是偽隨機數?”
CPU阿甘說:“老大說了,要不可預測,但是你這公式,我如果知道了上一個隨機數,下一個隨機數我自己代入那個公式就可以計算出來了,在我這里,一毫秒都用不了,完全可以預測。”
阿甘此言不虛,他的速度是整個計算機系統最快的。
"還有,你居然用當前時間做種子,那我也用同樣的時間做種子,豈不是可以生成和你一模一樣的隨機數隊列?完全可以重現啊。”
C老頭兒連續挨了兩記悶棍,嘴里嘟囔著,但也說不出話來了。
Linux老大趕緊和稀泥:“雖然是偽隨機數,但是這個算法非常簡單,對于那些對安全要求不高的場合,比如玩游戲的時候,還是非常有用的。我們再想想,怎么生成真正的隨機數吧!”
C老頭兒說到:要不這樣,我們可以使用Hash函數。
- R1 = hash(seed)
- seed = seed + 1
- R2 = hash(seed)
- seed = seed + 1
- R3 = hash(seed)
- seed = seed + 1
- R4 = hash(seed)
- ......
CPU阿甘說:“這個方法還行,在不知道種子(seed)的情況下,你給我一個隨機數,我是無法預測下一個的,因為隨機數是hash函數生成的,是個單向的過程。但是,如果我知道了種子,那就可以生成和你一模一樣的隨機數列,所以不滿足‘不可重現’的性質。”
看來生成真正的隨機數太難了,大家都沉默了。
過了良久, vim突然說到:你們以為我說的是笑話,但是思路卻是可以借鑒啊?大家想想
用戶敲擊鍵盤的速度節奏是不是隨機的?
用戶的鼠標移動是不是隨機的?
網卡每秒發送的數據量是不是隨機的?
硬盤每秒寫入的數據是不是隨機的?
如果我們把這些隨機的東西給綜合起來......
Linux老大非常高興:“沒錯,我們可以把它們認為是機器運行的環境噪音,我把它們收集起來放到一個池子里......”
CPU阿甘馬上接口:“然后,可以用個Hash算法對這個池子中的內容做個消息摘要,結果就是真隨機數了!雜亂無章,無法預測,無法重現。”
vim感覺有點不爽,這倆人也太會搶功勞了。
C老頭兒也頻頻點頭:“這個辦法妙啊,我可以修改一下我的rand函數,來獲得這個值......”
Linux老大:“別別,你的偽隨機數還是要保留,上周碼農翻身公眾號剛剛說過,一切皆文件,我可以生成一個特殊的文件,就叫/dev/random吧,這樣程序員就可以使用最常用的open ,read等方法來調用了!”
Linux老大說完,又感慨了一句:“終于,我們學會擲骰子了!”
一天以后。
CPU阿甘興沖沖地跑來找Linux老大:老大,昨天忘了一件事,我的硬件就支持真正的隨機數生成啊,我可以利用電阻的熱噪聲來生成的,是真隨機數,用RdRand指令就能獲得。
Linux老大:這個硬件是你出生的時候就被植入了,是個黑盒子,如果NSA在里邊安裝了后門,怎么辦?
CPU阿甘看著自己的身體,愣住了......
【本文為51CTO專欄作者“劉欣”的原創稿件,轉載請通過作者微信公眾號coderising獲取授權】