熵品質測試

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

理論疑慮

大致上來說,有時在識別出數字資料流時,不會因為辨識出模式而隨機產生。我們無法確認這些是真正的隨機數字。最先進的資料似乎正在對資料執行多項統計測試,並希望能偵測任何可遭利用的弱點。

當隨機數字不完全隨機 (或是序列中的數字之間存在有限度) 時,隨機數的測試會變得更加困難。不完美的隨機數串流仍會包含一些隨機性,但要決定隨機數並不容易。

就我們的目的而言,零熵隨機數串流中包含多少隨機性,是很好的測量指標。這與資訊理論中使用的 Shannon 熵相關,但一律需要較小的值。 最小熵可控制我們可從熵來源擷取的隨機程度。詳情請參閱 https://en.wikipedia.org/wiki/Randomness_extractor#Formal_definition_of_extractors

從實際角度來看,我們可以使用美國 NIST SP800-90B 中所述的測試套件,分析熵來源的隨機樣本。如需測試的原型實作,請前往 https://github.com/usnistgov/SP800-90B_EntropyAssessment。套件採用範例資料檔案 (例如 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 收集 100 萬位元組、使用 AES-256 加密這些位元組,但密碼強度較低,也沒有鹽 (當然是很糟糕的加密機制!)。事實上,輸出內容看似優良的隨機資料,代表 AES 會正常運作,但這可以證明從處理的資料中預估熵內容的風險:同時,/dev/zero 和「password」可提供約 0 位元的熵,但我們的測試對於產生的資料會更加樂觀!

如需更具體的 Zircon 相關範例,請考慮使用 jitterentropy (RNG 章節:http://www.chronox.de/jent/doc/CPU-Jitter-NPTRNG.html)。 追蹤時基誤差會根據 CPU 時間的變化繪製熵。未處理的資料是指執行特定區塊會耗用大量 CPU 和記憶體的程式碼區塊 (以奈秒為單位)。當然,這些時間資料並非完全隨機排序,而是這些時間資料的平均值,會有些波動。每個個別資料樣本可能是好幾個位元 (例如 64 位元整數),但只有 1 位元以下的最小熵。

完整的時基誤差 RNG 程式碼會使用多個原始時間資料樣本,將這些樣本處理為單一隨機輸出 (透過 LFSR 切換等等)。如果我們測試處理後的輸出內容,就會發現從實際時間變化和 LFSR 都發現了明顯的隨機性。我們只想專注在時間變化,因此應測試原始時間樣本。請注意,如有需要,您可以透過 kernel.jitterentropy.raw cmdline 開啟或關閉時基誤差的內建處理功能。

品質測試導入作業

如上所述,NIST 測試套件會接受充滿隨機位元組的檔案做為輸入。我們會在 Zircon 系統上收集這些位元組 (可能位於上方較窄的 Fuchsia 層),然後再將其匯出至效能更高的工作站來執行測試套件。

啟動時間測試

系統會在啟動期間讀取部分熵來源,然後再啟動使用者空間。為了在實際環境中測試這些熵來源,我們會在啟動時執行測試。相關程式碼位於 kernel/lib/crypto/entropy/quality\_test.cpp 中,但基本概念是核心概念是,核心會分配大型靜態緩衝區,以在早期啟動期間保留測試資料 (在 VMM 啟動之前,因此在 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,這個常數 (如有提供) 是靜態分配的緩衝區大小。如未指定,則預設值為 1MiB。

啟動期間測試:設定

啟動期間測試是透過核心 cmdline 控制。相關 cmdline 是 kernel.entropy-test.*,記錄在 kernel_cmdline.md 中。

有些熵來源 (特別是時基誤差),提供可透過核心 cmdline 調整參數值。詳情請參閱 kernel_cmdline.md

開機時測試:執行中

只要啟動了正確的核心 cmdline,系統會在開機期間自動執行啟動測試 (如果分行發生問題,系統會改為列印錯誤訊息)。測試會在 RNG 播種的第一階段 (在 LK_INIT_LEVEL_PLATFORM_EARLY ) 之前執行,隨後在 VMM 的堆積中不久前進行。如果是執行大型測試,則開機速度通常會大幅降低。舉例來說,根據參數值,從 Rpi3 收集 128kB 的資料可能需要一分鐘的時間。

執行階段測試

TODO(https://fxbug.dev/42098992):討論實際的使用者模式測試流程

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

測試資料匯出

測試資料會儲存在測試下的 Zircon 系統 /boot/kernel/debug/entropy.bin 中。目前我通常透過 netcp 手動匯出資料檔案。 如果您使用正確的 Fuchsia 套件進行建構,或儲存至永久儲存空間,其他選項則包含 scp

執行 NIST 測試套件

注意:NIST 測試實際上並未在 Fuchsia 中呈現。目前,您需要從存放區複製測試,網址為 https://github.com/usnistgov/SP800-90B_EntropyAssessment

NIST 測試套件有三個進入點 (截至 2016 年 10 月 25 日修訂的版本):iid_main.pynoniid_main.pyrestart.py。兩個「main」指令碼會執行大量的工作。iid_main.py 指令碼適用於熵來源,這類來源會產生完全相同且分佈相同的資料樣本。大多數的測試都是驗證 iid 狀況。許多熵來源不會是 iid,因此 noniid_main.py 測試會實作多個不需要 iid 資料的熵 Estimator。

請注意,NIST 存放區中的測試二進位檔是不含 Shebang 行的 Python 指令碼,因此叫用這些二進位檔時,您可能需要在指令列中明確呼叫 python

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

noniid_main.py 也選擇性接受 -u <int> 旗標,以減少在第二個必要引數中傳遞的 N 值以下的位元數。我不太確定為何要提供這個標記;它看起來好像多餘,但傳遞它會稍微改變詳細輸出內容。我猜想的是,由於非同類型的 Markov 測試只能處理最多 6 位元的樣本,因此在本測試中,系統會將 7 或 8 位元的資料集縮減至低 6 位元,因此才會提供這項測試。相較之下,所有 iid 測試都可以在 8 位元樣本上執行。

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

python2 -- $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。

未來路線

自動化

可以自動處理建構、設定及執行品質測試的程序。第一步應能輕鬆撰寫殼層指令碼來執行這些步驟。更好的做法是使用測試基礎架構自動執行熵收集器品質測試,主要是減少測試程式碼的位元旋轉。自動化失敗,我們必須仰賴真人定期執行測試 (或在測試中斷時修正測試)。