前言
關於隨機數測試,有兩份比較常用的標准,一份是國密局的隨機數檢測規范,一份是NIST的測試標准
國密局標准參看GB/T 32915,NIST標准可以參看 https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-22r1a.pdf
兩者大體上一致,存在幾個用例的區別,另外NIST除了統計P值總數,還統計P值的分布
NIST還提供了一個測試程序,可以從這里獲取 https://csrc.nist.gov/projects/random-bit-generation/documentation-and-software
關於隨機數
-
隨機數特性
- 隨機性
一個理想的隨機二進制比特流,可由一個完全"公正無偏"的通過拋擲正反面得到,其中正面記為0,反面記為1。每一擲得到0和1的概率都是確切的1/2。而且每一次的結果之間都相互獨立,即先前的結果不會對未來的結果產生影響
使用像“公正硬幣”這樣的理想隨機數發生器是不實際的,但是這樣一個理想的發生器產生的輸出可以作為我們測試評估其他隨機數發生器的基准。
- 不可預測性
加密應用的隨機數需要不可預測。對於偽隨機數發生器,在不知道種子的情況下,就算知道先前的隨機數序列也應該無法預測接下來輸出,也即前向不可預測性。同時后向不可預測性也是要求的,即無法通過已知的產生的值,得出種子。
由於偽隨機數算法往往已知,確保前向不可預測性,在得到種子的方法上需要注意,種子本身要不可預測。
-
產生方法
-
RNG(隨機數發生器)
使用熵源和熵蒸餾函數。其中熵蒸餾函數用來克服熵源的任何非隨機性缺陷。熵源一般基於物理現象由硬件產生,得到真隨機數。特別地,像我們即將評估的量子隨機數發生器,就是利用了半導體的量子效應。
RNG產生的結果可以直接作為隨機數使用,但前提是已經滿足了嚴格的隨機性標准。也可以將得到的結果再喂給PRNG,或者將幾個PNRG的結果進行結合,以提高隨機數的隨機性。
-
PRNG(偽隨機數發生器)
提供輸入的種子,通過數值運算,得到偽隨機數。偽隨機數發生器的輸出是種子的一個確定性結果,即喂給相同的種子的情況下輸出永遠一樣。這也是為什么稱之為“偽隨機數”的原因。所以輸入的種子本身需要滿足隨機性和不可預測性。
偽隨機發生器便於重現,產生速度快,有其自身的優點。
-
-
測試
隨機數的測試,基於隨機性假設設計測試用例進行統計性的驗證。每一個統計測試用例用來驗證一個特定的假設。根據測試的統計結果決定是假設成立還是不成立。在隨機性假設下,統計量會滿足一個參考分布,從這個參考分布上確定一個臨界值(比如99%)。測試的時候,將測試結果同這個臨界值進行比較,如果通過小於臨界值則認為假設成立,反之則認為不成立。
考慮到假設不成立本身的概率很小,需要測試多個樣本(至少在alpha值倒數的量級),通過統計所有樣本的結果,最終決定測試是否通過。
可能的統計測試是無窮的,評估任何一種特定的pattern是否存在。沒有那個有窮的測試集合是完備的,所以在測試結果的解釋上要謹慎。
測試用例
-
頻數檢測
目的是檢測待測試二進制序列中,“0”和“1” 數目是否近似相等。如果是,則認為序列是隨機的。
-
塊內頻數檢測
目的是確定在待測序列中,所有非重疊的長度為M位的塊內的“0”和“1”的數目是否表現為隨機分布。如果是,則序列是隨機的。
-
游程檢測
目的是確定待測序列中,總的游程數目是否如真隨機序列期望的那樣。如果是,則序列是隨機的。
-
塊內最長游程檢測
目的是確定待測序列中,最長“1”游程的長度是否與真隨機序列中最長“1”游程的長度近似一致。如果是,則序列是隨機的。
-
矩陣秩檢測
目的是檢測待測序列中,固定長度子序列的線性相關性。如果線性相關性較小,則序列是隨機的。
-
離散傅里葉變換檢測
目的是通過檢測待測序列的周期性質,並與真隨機序列周期性質相比較,通過它們之間的偏離程度來確定待測序列隨機性。如果偏離程度較小,序列是隨機的。(超過95%閾值的峰數是否顯著異於5%)
-
非重疊模板匹配檢測
目的是檢測待測序列中,子序列是否與太多的非重疊模板相匹配。太多就意味着待測序列是非隨機的。
-
重疊模板匹配檢測
目的是統計待測序列中,特定長度的連續“1”的數目,是否與真隨機序列的情況偏離太大。太大是非隨機的。
-
通用統計檢測
目的是檢測待測序列是否能在信息不丟失的情況下被明顯壓縮。一個不可被明顯壓縮的序列是隨機的。(匹配pattern(壓縮序列長度相關的度量)之間的比特數)
-
線性復雜度檢測
目的是確定待測序列是否足夠復雜,如果是,則序列是隨機的。
-
重疊子序列檢測
目的是確定待測序列所有可能的m位比特的組合子串出現的次數是否與真隨機序列中的情況近似相同,如果是,則序列是隨機的。每個pattern的概率相等
-
近似熵檢測
目的是通過比較m位比特串與m+1位比特串在待測序列中出現的頻度,再與正態分布的序列中的情況相對比,從而確定隨機性。
-
累加和檢測
目的確定待測序列中的部分和是否太大或太小。太大或太小都是非隨機的。
-
隨機游走檢測
目的是確定在一次隨機游走過程中,某個特定狀態出現的次數為K的cycle個數是否遠遠超過真隨機序列中的情況。如果是,則序列是非隨機的。
-
隨機游走變量檢測
目的是檢測待測序列中,某一特定狀態在一個游走過程中出現的總次數與真隨機序列的偏離程度。如果偏離程度較大,則序列是非隨機的。
>以下幾個國密特有
-
游程分布檢測
目的是確定待測序列中,游程的分布情況是或否與真隨機序列近似。如果偏離程度較大,則序列是非隨機的。
-
撲克檢測
目的是檢測待測序列中,子序列是否與太多的非重疊模板相匹配。太多就意味着待測序列是非隨機的
-
二元推導檢測
將初始序列中相鄰兩比特依次做異或操作得到新序列,這樣在第k次二元推導的序列中0和1的數量是否接近一致。如果與真隨機序列相比接近,則認為是隨機的。
-
自相關檢測
檢測待測序列與將其自身邏輯左移d位后的新序列的關聯程度,如果關聯程度與真隨機序列相比偏高,則認為是非隨機的。
測試步驟
-
准備測試數據
使用待測的(偽)隨機數發生器,產生足夠長度的隨機數序列。(樣本參數見附錄)
-
在NIST的測試源碼中,添加4個國密特有的測試用例
撲克測試,游程分布測試,二元推導測試,自相關測試
-
編譯源碼得到測試程序
在sts-2.1.2目錄下make即可
-
運行測試程序,選擇待測試數據,按照NIST測試用例和測試參數進行設置
- 執行測試程序,
$ ./assess 1000000
2. 輸入0,選擇從文件讀入隨機數
3. 輸入隨機數路徑及文件名,例如data/data1.bin
4. 輸入0,選擇NIST測試模式
5. 輸入2,選擇測試所有NIST用例
6. 輸入0,NIST用例使用默認參數即可(如果需要修改參數輸入相應的編號進行修改)
7. 輸入1000,選擇測試的樣本數量
8. 輸入1,選擇輸入的文件類型為二進制(選擇0表示全是01的ASCII文件)
9. 按回車即開始測試
- 執行測試程序,
-
測試結束之后,分析測試結果,看是否滿足預期要求
-
運行測試程序,選擇待測試數據,按照國密規范選擇對應測試用例,並進行參數設置
- 執行測試程序
$ ./assess 1000000
2. 輸入0,選擇從文件讀入隨機數
3. 輸入隨機數路徑及文件名,例如data/data1.bin
4. 輸入1,選擇國密測試模式,固定為國密的測試參數
5. 輸入3,選擇測試所有GM用例
6. 輸入1000,選擇測試的樣本數量
7. 輸入1,選擇輸入的文件類型為二進制(選擇0表示全是01的ASCII文件)
8. 按回車即開始測試
- 執行測試程序
-
測試結果之后,分析測試結果,看是否滿足預期要求
結果分析
-
通過測試的樣本比例(NIST標准)
根據樣本總個數和顯著性水平,計算判定用例通過的最小樣本個數
對於樣本數為1000,顯著性水平為0.01的情況,通過的比例需要在0.98以上
即1000的樣本需要有980個通過。 -
P值均勻分布(僅NIST)
對每個測試樣本的P值結果,按照0.1的區間間隔進行數量統計,計算P值統計結果的P值。
如果最終的P值大於0.0001,則認為是均勻分布的。
附錄
國密和NIST隨機性測試參數設置表
序號 | 檢測項目 | 國密參數 | NIST參數 |
---|---|---|---|
1 | 塊內頻數測試 | m=100 | m=128 |
2 | 撲克測試(國密) | m=4; m=8 | / |
3 | 重疊子序列測試 | m=2; m=5 | m=16 |
4 | 塊內最長游程測試 | m=10000 | m=10000 |
5 | 二元推導測試(國密) | k=3; k=7 | / |
6 | 自相關測試(國密) | d=1, 2, 8, 16 | / |
7 | 矩陣秩測試 | M=Q=32 | M=Q=32 |
8 | 近似熵測試 | m=2; m=5 | m=10 |
9 | 線性復雜度測試 | m=500 | m=500 |
10 | 非重疊模板測試(NIST) | / | m=9 |
11 | 重疊模板測試(NIST) | / | m=9 |
12 | 通用測試 | L=7,Q=1280 | L=7,Q=1280 |
顯著性水平α=0.01
樣本參數
參數 | 值 |
---|---|
樣本長度 | 10^6 bits |
樣本個數 | 1000 |
隨機數樣本生成方法
-
openssl隨機數樣本生成方法(1.0.2o)
openssl rand -out data.openssl 1000000000
-
用C語言中rand函數生成隨機數樣本(glibc版本2.27)
./c_rand 1024 data.c_rand 1000000000
其中c_rand程序的源碼c_rand.c如下
#include <stdlib.h> #include <stdio.h> #define buffer 1024 int main(int argc, char *argv[]) { int j, r, nbytes; unsigned int seed; FILE* fp = NULL; size_t res = 0; if (argc != 4) { fprintf(stderr, "Usage: %s <seed> <rand_file> <nbytes>\n", argv[0]); return -1; } seed = atoi(argv[1]); nbytes = atoi(argv[3]); srand(seed); unsigned char* out = new unsigned char[buffer]; unsigned char* cur = out; if (!(fp = fopen(argv[2], "wb"))) { printf("file %s open fail!", argv[2]); delete[] out; return -1; } for (j=0;j<nbytes; ++j) { r = rand()%256; *cur++ = *((unsigned char*)(&r)); if((!(( j + 1 ) % buffer)) || ( j + 1 == nbytes)) { size_t num = j % buffer + 1; res = fwrite(out, 1, num, fp); if (res != num) { fclose(fp); delete[] out; printf("file %s write fail!", argv[2]); return -1; } cur = out; } } fclose(fp); delete[] out; return 0; }
-
采集握手中ClientHello隨機數
生成隨機數命令:
openssl s_server -engine ./engine.so -cert test/rsa-ext.pem -key test/rsa.key -CAfile ca/rsa-ca.pem -www -accept 8888 ./ssl -h 192.168.1.10 -p 8888 -s 128000000 -f binary -o data.handshake -m tls1_2
-
采集硬件加密卡生成的隨機數樣本
前置條件:完成硬件安裝,驅動安裝,編譯openssl 引擎得到engine.so
openssl rand -engine ./engine.so -out data.cryptocard 1000000000