熵品質測試

本文說明我們如何測試用於播種 Zircon CPRNG 的熵來源品質。

理論疑慮

大致來說,有時只要辨識出數字串中的模式,就能輕易判斷該串數字並非隨機。我們無法確定這些數字是否真的隨機。目前最先進的做法似乎是對資料執行多項統計測試,希望偵測到任何可利用的弱點。

如果隨機數並非完全隨機 (例如分布不均,或序列中的數字之間存在有限的關聯性),測試隨機性的問題就會變得更加困難。非完美隨機數串流仍包含一些隨機性,但很難判斷隨機程度。

就我們的用途而言,衡量非完美隨機數串流中隨機性的良好指標是最小熵。這與資訊理論中使用的香農熵有關,但值一律較小。最小熵可控制我們能從熵來源可靠擷取的隨機程度;詳情請參閱「隨機性擷取器#擷取器的正式定義」。

從實務角度來看,我們可以運用美國 NIST SP800-90B 中所述的測試套件,分析熵來源的隨機樣本。如要瞭解測試的實作原型,請參閱 NIST SP800-90B 熵評估。這個套件會將範例資料檔案 (例如 1 MB 的隨機位元組) 做為輸入內容。這個測試套件的優點在於可以處理不完美的 RNG,並估算隨機資料串流中每個位元組所含的最小熵。

測試未處理資料的重要性

從熵源提取熵後,我們會以「安全」的方式將熵混合到 CPRNG 中,基本上會消除熵源原始隨機位元組串流中可偵測到的關聯性和分布不均。實際產生要使用的隨機數字時,這項作業非常重要,但在測試熵來源本身時,我們必須避免這個混合和處理階段。

以下實驗可清楚說明,如要測試實際的熵來源,為何必須測試未經處理的資料。只要安裝 OpenSSL,這個指令碼應可在任何新式 Linux 系統上執行。

    head -c 1000000 /dev/zero >zero.bin
    openssl enc -aes-256-ctr -in zero.bin -out random.bin -nosalt -k "password"

這會從 /dev/zero 取得一百萬個位元組,並透過 AES-256 加密,使用低強度密碼且沒有鹽 (當然,這是很糟糕的加密機制!)。輸出內容看起來像是良好的隨機資料,表示 AES 運作正常,但這也顯示出從處理過的資料估算熵內容的風險:/dev/zero 和「password」加起來提供約 0 位元的熵,但我們的測試對產生的資料非常樂觀!

如需更具體的 Zircon 相關範例,請考慮 jitterentropy (CPU-Jitter-NPTRNG 中討論的 RNG)。Jitterentropy 會從 CPU 時序的變化中提取熵。未處理的資料是指執行特定 CPU 和記憶體密集型程式碼區塊所需的時間 (以奈秒為單位)。當然,這些時間資料並非完全隨機,而是以平均值為中心,並有部分波動。每個個別資料樣本可能為數個位元 (例如 64 位元整數),但只會貢獻 1 個位元或更少的最小熵。

完整的 jitterentropy RNG 程式碼會擷取多個原始時間資料樣本,並將其處理為單一隨機輸出 (包括透過 LFSR 轉移)。如果測試處理後的輸出內容,會發現實際時間變化和 LFSR 都呈現明顯的隨機性。我們只想專注於時間變化,因此應測試原始時間樣本。請注意,您可以透過 kernel.jitterentropy.raw cmdline 開啟及關閉 jitterentropy 的內建處理程序。

實作品質測試

如上所述,NIST 測試套件會將包含隨機位元組的檔案做為輸入內容。我們會在 Zircon 系統上收集這些位元組 (可能在頂端加上薄型 Fuchsia 層),然後通常會將這些位元組匯出至功能更強大的工作站,以執行測試套件。

開機時測試

部分熵來源會在啟動期間讀取,也就是在啟動使用者空間之前。 如要在實際環境中測試這些熵來源,我們會在啟動期間執行測試。相關程式碼位於 kernel/lib/crypto/entropy/quality\_test.cpp,但基本概念是核心會分配大型靜態緩衝區,在早期啟動期間 (VMM 啟動前,因此無法分配 VMO) 保存測試資料。稍後,資料會複製到 VMO,而 VMO 會傳遞至使用者啟動程序和 devmgr,並以 /boot/kernel/debug/entropy.bin 的虛擬檔案形式呈現。使用者空間應用程式可以讀取這個檔案並匯出資料 (例如複製到永久儲存空間或使用網路)。

理論上,您應該可以使用 scripts/entropy-test/make-parallel 建構 Zircon,並啟用熵收集器測試,然後使用 scripts/entropy-test/run-boot-test 指令碼執行單一啟動時間測試。run-boot-test 指令碼主要用於由其他指令碼叫用,因此有些粗略 (例如,大部分引數都是透過指令列選項 (如 -a x86-64) 傳遞,但許多「選項」實際上是必要項目)。

假設 run-boot-test 指令碼成功執行,輸出目錄中應會產生兩個檔案:entropy.000000000.binentropy.000000000.meta。第一個是從熵來源收集的原始資料,第二個是簡單的文字檔,每行都是鍵/值組合。鍵是符合 /[a-zA-Z0-9_-]+/ 的單字,值則以符合 /[ \t]+/ 的空白字元分隔。透過 Bash 中的 read、Python 中的 str.split(),或 C 中的 scanf (請注意緩衝區溢位問題),即可輕鬆剖析這個檔案。

實際上,我擔心這些指令碼會發生位元腐敗,因此接下來的幾個章節會記錄指令碼的預期行為,方便手動執行測試,或在指令碼中斷時修正。

開機時間測試:建構

由於開機時間熵測試需要永久保留大量記憶體區塊 (用於暫時的 VMM 前緩衝區),因此我們通常不會將熵測試模式建構到核心中。如要啟用測試,請在建構時間傳遞 ENABLE_ENTROPY_COLLECTOR_TEST 旗標,例如新增以下程式碼行:

EXTERNAL_DEFINES += ENABLE_ENTROPY_COLLECTOR_TEST=1

local.mk。目前還有一個建構時間常數 ENTROPY_COLLECTOR_TEST_MAXLEN,如果提供這個常數,則為靜態分配緩衝區的大小。如未指定,預設值為 1 MiB。

開機時測試:設定

開機時的測試是透過核心指令列控制。相關的 cmdline 為 kernel.entropy-test.*,請參閱核心指令列

部分熵來源 (尤其是 jitterentropy) 的參數值可透過核心指令列調整。詳情請參閱核心指令列

開機時測試:執行中

只要傳遞正確的 Kernel cmdline,開機時就會自動執行開機時間測試 (如果 cmdline 有問題,系統會改為列印錯誤訊息)。測試會在 RNG 播種的第一階段之前執行,也就是 LK_INIT_LEVEL_PLATFORM_EARLY,VMM 堆積之前不久。如果執行大型測試,開機速度通常會明顯變慢。舉例來說,視參數值而定,在 rpi3 上從 jitterentropy 收集 128 KB 的資料可能需要約一分鐘。

執行階段測試

目前初步想法:只有核心可以觸發 hwrng 讀取作業。如要測試,使用者空間會發出核心指令 (例如 k hwrng test),並提供一些引數來指定測試來源和長度。核心會將隨機位元組收集到 /boot/kernel/debug/entropy.bin 的現有 VMO 支援虛擬檔案中,前提是這個檔案可安全寫入。目前尚未實作;因缺少使用者空間 HWRNG 驅動程式庫而遭到封鎖。可以先測試 VMO 重寫機制。

測試資料匯出

測試資料會儲存在受測 Zircon 系統的 /boot/kernel/debug/entropy.bin 中。到目前為止,我通常是透過 netcp 手動匯出資料檔案。 其他選項包括:使用正確的 Fuchsia 套件建構,或儲存至永久儲存空間。scp

執行 NIST 測試套件

NIST 測試套件有三個進入點 (截至 2016 年 10 月 25 日提交的版本):iid_main.pynoniid_main.pyrestart.py。這兩個「主要」指令碼會執行大部分的工作。iid_main.py 指令碼適用於產生獨立同分布資料樣本的熵來源。大部分測試都是為了驗證 iid 條件。許多熵來源並非 iid,因此 noniid_main.py 測試會實作多個不需要 iid 資料的熵估算器。

請注意,NIST 存放區的測試二進位檔是沒有 Shebang 行的 Python 指令碼,因此您可能需要在指令列上明確呼叫 python3 來叫用這些指令碼。

前兩個指令碼會採用兩個引數,兩者皆為必要引數:要讀取的資料檔案,以及每個樣本的有效位元數 (如果少於 8 個,則每個位元組只會使用低 N 位元)。這些指令可選擇接受 -v 旗標,產生詳細輸出內容,或接受 -h 旗標以取得說明。

noniid_main.py 也可選擇接受 -u <int> 標記,該標記可減少第二個必要引數中傳遞的 N 值以下的位元數。我不完全確定為何要提供這個標記,因為這似乎在功能上是多餘的,但傳遞這個標記確實會稍微改變詳細輸出內容。我認為這是因為非 iid 馬可夫測試僅適用於最多 6 位元的樣本,因此 7 或 8 位元的資料集會縮減為低 6 位元,以進行這項測試。相較之下,所有 iid 測試都可以在 8 位元樣本上執行。

iid_main.py 指令碼的叫用範例:

python3 -- $FUCHSIA_DIR/third_party/sp800-90b-entropy-assessment/iid_main.py -v /path/to/datafile.bin 8

restart.py 指令碼會採用相同的兩個引數,外加第三個引數:先前執行 iid_main.pynoniid_main.py 時傳回的最小熵估計值。本文不會說明如何重新啟動測試。目前請參閱 NIST SP800-90B,瞭解更多詳情。

未來發展方向

自動化

如果能自動執行建構、設定及執行品質測試的程序,那就太好了。首先,您應該可以輕鬆編寫殼層指令碼,執行這些步驟。如果能使用測試基礎架構自動執行熵收集器品質測試,效果會更好,這主要是為了減少測試程式碼中的位元腐敗。如果自動化失敗,我們就必須仰賴人工定期執行測試 (或在測試中斷時修正)。