先說結論
這個問題背後的疑心恰恰是對的,留著它。造密碼的頁面,是全網唯一一種收不到任何敏感東西、卻能留下全部敏感東西的頁面:它的產出本身就是秘密。一個把產出發出去的產生器,不是工具不行,是密碼收集器。問題從來不是這個產生頁面看著可不可信,而是它想留的話,留不留得下。而這件事,難得地,查得了。
三樣東西定勝負:隨機數從哪裡來,結果能不能離開頁面,結果有沒有哪一點能被預測。三樣都有老實的答案,訪客自己就能驗證。一個下載來的應用,在誰也看不進去的視窗裡產生,可沒有這待遇。
瀏覽器的隨機數從哪裡來
瀏覽器裡正經的產生器,打的都是同一口井:crypto.getRandomValues,瀏覽器的密碼學隨機數發生器,由作業系統用硬體雜訊播種,而且不停地補種。瀏覽器取 TLS 金鑰用的也是這口井,你和銀行之間的加密就跑在那上面。桌面程式能摸到比網頁更好的隨機數?這話不成立,兩邊最後都落到同一口系統的井裡。
頁面不該用的是 Math.random(),擲骰子用的家常函式。瀏覽器用一個很快的產生器來實現它,而那個產生器的內部狀態,看上手邊幾個連續輸出就能重建。也就是說,架在它上面的密碼看著隨機,誰見過其中一個,就都算得出其餘。這不是紙上談兵,對市面上的產生器演示過不止一次。而且從外面看不出來,這正是程式碼能讀的產生器最硬的一條理由:兩個函式的差別,在原始碼裡就一個詞。
再往上還有一層細緻。把隨機的 32 位字用求餘數變成「26 以內的數」,會朝前幾個字母有一絲絲偏;講究的產生器不取餘數,重抽。這裡的產生器就是這麼做的。它避開的偏差大約是一億六千五百萬分之一,用起來看不見,但恰恰是這種細節,把為這件事造的工具和論壇上抄來的程式碼片段分開。
一個壞的產生頁面能做什麼
把砸鍋的方式一條條點名,因為每一條都查得了。
- 把密碼發出去。頁面在本地產生,然後把造好的發走。隨點選一起,捎在統計裡,或者攢著以後發。這一條是一票否決,而且看得見:它必須是一次網路請求,請求盯得住。
- 在伺服器上產生。密碼不是從網路出去,是從網路來的。那運營者比你先見到它,它怎麼造的你也無從知道。同一個檢查,反著做。
- 產生得弱。
Math.random,拿時間戳當種子,幾百個詞的詞表冒充強度。這一條網路面板抓不住,只有讀得了的原始碼抓得住,或者一條按真實設定數出來的老實強度提示。 - 留歷史。熱心地替你記著最近二十個密碼。存在比分頁活得久的地方,擺在一臺可能是共用的機器上。
這個站的密碼與口令短語產生器,從造法上就把四條都堵死了:只用 crypto.getRandomValues,產生在頁面裡完成,任何形式的儲存都沒有,沒有歷史,還有一行強度提示,把你這套設定下一共有多少種可能的結果數得清清楚楚。口令短語用的詞表是 EFF 的 Diceware 詞表,一字未動,隨工具自己的資料夾一起帶著。
怎麼檢查任何一個產生器,包括這個
完整的方法寫在上傳那一頁。要挑一個頁面先試,就試這個:
- 先拔網線。把頁面開啟,斷網,然後再產生。沒有連線時造出來的密碼,不可能是外面取來的,也不可能在出生那一刻被送走。這個頁面斷網照常工作,這就是它的意義。
- 產生時盯著網路面板。按下按鈕,讀那份清單:什麼都不該出去。然後複製密碼,再看一眼。複製那一下,正是不老實的頁面會挑的時機。
- 找收集器才需要的東西。帳號,同步,「最近產生」的清單。有記性的產生器,手裡就有副本。
結尾配得上一句老實話。檢查告訴你的,是你盯著看時頁面做了什麼;公開發布、原樣可讀的程式碼,這個站上的一切都是如此,告訴你的是它一般會做什麼。剩下的是機器本身:沒有哪個網頁能替你把密碼擋住被攻破的瀏覽器或者記鍵盤的惡意軟體,產生器也不例外。檢查替你換來的東西更小,也更實在:一個任何伺服器都從沒見過的密碼,出自一段允許你親自讀一遍的算術。