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