OpenSSL v0.9.8a隨機數發生器分析(合集)
OpenSSL隨機數發生器
摘要:本文件對OpenSSL使用的隨機數進行研究分析,主要涉及OpenSSL v0.9.8a的隨機數發生器以及其在Windows系統下的熵源採集情況。
關鍵詞:OpenSSL,密碼模組,隨機數發生器,密碼演算法,雜湊演算法,熵。
目錄
1. 概況
OpenSSL不同版本使用的隨機數發生器不太相同,主要差別在於:
- 非FIPS版本:預設使用OpenSSL內部整合的一款基於HASH演算法(預設為SHA1)的隨機數發生器;熵源則根據不同的系統有不同的生成方式。
- FIPS版本:使用NIST SP 800-90A的三種隨機數發生器,熵源由呼叫者通過回撥函式的方式從外部提供。
非FIPS版本的熵源,根據不同的系統有不同的生成方式,
- Windows系統見.\crypto\rand\rand_win.c檔案。
- 低版本的OpenSSL使用大量系統資訊作為熵(本文研究重點)。
- 但高版本的則依賴於Windows系統提供的隨機數,在Windows 7或更高版本中使用BCrypt代替了CryptoAPI。
- 其他系統的熵源生成方式均可見.\crypto\rand\資料夾。Linux系統使用系統提供的熵源,同樣不同大版本使用的熵源差異較大。
下表是OpenSSL幾個不同版本使用的隨機數發生器的概況。
表1 OpenSSL不同版本使用的隨機數發生器概況表
版本 |
隨機數發生器 |
Windows下熵源 |
OpenSSL 0.9.8a |
自己定義一套隨機數發生器 見.\crypto\rand\md_rand.c |
使用大量系統資訊 |
OpenSSL 1.1.0 |
同上 |
僅使用Windows提供的隨機數API,不再使用大量的系統資訊。在Windows 7或更高版本中使用BCrypt代替了CryptoAPI。 |
OpenSSL FIPS 2.0.11 |
NIST SP 800-90A的三種隨機數發生器 見.\ fips\rand\目錄 |
外部提供 |
本文研究OpenSSL 0.9.8a的隨機數發生器以及熵源的詳情。
OpenSSL在Linux系統下使用的熵源可參見rand_unix.c檔案。
OpenSSL FIPS 2.0.11使用的NIST SP 800-90A的三種隨機數發生器參見《NIST SP 800-90系列(隨機數發生器)筆記》。
內部預設的隨機數發生器程式碼參見.\crypto\rand\md_rand.c檔案,這裡提供了隨機數發生器的狀態資訊以及一系列的呼叫函式
- ssleay_rand_seed 新增種子
- ssleay_rand_bytes 產生隨機資料
- ssleay_rand_cleanup 置零
- ssleay_rand_add 新增資訊
- ssleay_rand_pseudo_bytes 產生偽隨機資料
- ssleay_rand_status 獲取狀態資訊
熵源
- RAND_poll 不同版本使用不同的熵源產生方式
2. 隨機數發生器內部狀態
程式碼參見.\crypto\rand\md_rand.c檔案
隨機數發生器的內部狀態定義為全域性變數(變數名與檔案中描述對應),即下表的資料均為static型。
STATE_SIZE 1023
MD_DIGEST_LENGTH 預設使用SHA1演算法,所以為20
ENTROPY_NEEDED 所需的熵值,單位位元組,值為32
表2隨機數發生器內部狀態
資料 |
長度 |
型別 |
含義 |
state |
STATE_SIZE+ MD_DIGEST_LENGTH位元組 |
BYTE |
內部狀態資料,環形緩衝區。 |
state_num |
4位元組 |
int |
內部狀態實際有效位元組數 state_index <= state_num <= STATE_SIZE |
state_index |
4位元組 |
int |
緩衝區偏移量(當前應處理的內部狀態位置) |
md |
MD_DIGEST_LENGTH位元組 |
BYTE |
上一次新增種子(即rand_add)處理得到的摘要值 |
md_count[0] |
4位元組 |
long |
隨機數生成函式rand_bytes的呼叫次數 |
md_count[1] |
4位元組 |
long |
記錄新增種子函式rand_add送入種子的累積分塊數,每次送入的分塊數是 向上取整(種子長度/分塊長度), |
entropy |
8位元組 |
double |
熵值,單位位元組。 |
initialized |
1位元 |
BOOL |
是否初始化 |
3. 函式說明
預設的隨機數發生器的呼叫函式為(程式碼見.\crypto\rand\md_rand.c)
- ssleay_rand_cleanup 置零
- ssleay_rand_status 獲取狀態資訊
- ssleay_rand_seed 新增滿熵種子
- ssleay_rand_add 新增資訊
- ssleay_rand_bytes 產生隨機資料
- ssleay_rand_pseudo_bytes 產生偽隨機資料
熵源(Windows下的程式碼見.\crypto\rand\rand_win.c)
- RAND_poll 不同版本使用不同的熵源產生方式
函 數: void ssleay_rand_cleanup(void)
功能描述: 置零
說 明: 相當於init、uninit、reset
注 意: 無
引數說明: 無
返 回 值: 無
執行步驟:
步驟1:內部狀態全部清零/重置。
函 數: int ssleay_rand_status(void)
功能描述: 獲取狀態資訊
說 明: 無
注 意: 無
引數說明: 無
返 回 值: 當前的熵值entropy是否大於32位元組(256位元)
執行步驟:
步驟1:若未初始化(initialized為0),執行採集系統熵資訊的函式RAND_poll(),此函式將改變熵值entropy;並將initialized置為1。
步驟2:返回當前的熵值entropy是否大於32位元組(256位元)。
函 數: void ssleay_rand_seed(const void *buf, int num)
功能描述: 新增滿熵種子
說 明: 無
注 意: 這裡認為種子是滿熵的,所以將種子的熵值也設定為種子長度
引數說明:
buf (in) 種子
num (in) 種子的位元組長度
返 回 值: 無
執行步驟:
步驟1:直接呼叫ssleay_rand_add(buf, num, num)函式並返回。
函 數: int ssleay_rand_pseudo_bytes(unsigned char *buf, int num)
功能描述: 產生偽隨機數
說 明: 無
注 意: 這裡簡單地設定兩個函式ssleay_rand_pseudo_bytes和ssleay_rand_add是一樣,其實應有差別。
引數說明:
buf (out) 偽隨機數
num (in) 需要的偽隨機數的位元組長度
返 回 值: 1 [成功],0 [失敗]
執行步驟:
步驟1:直接呼叫ssleay_rand_add(buf, num, num)函式並返回。
函 數: void ssleay_rand_add(const void *buf, int num, double add)
功能描述: 新增熵資訊
說 明: 更新內部狀態;預設的HASH函式為SHA1,輸出長度為20位元組。
注 意: 新增資訊的熵值(add)可為小數。
引數說明:
buf (in) 新增資訊
num (in) 新增資訊的位元組長度
add (in) 新增資訊的熵值,單位:位元組。
返 回 值: 無
執行步驟:
步驟1:更新state_num(狀態的實際有效位元組數)
state_num =min(state_num + num,STATE_SIZE)
步驟2:將輸入資料buf以及內部狀態中的環形緩衝區state(state從state_index起的num位元組資料),分別按雜湊函式HASH的輸出大小MD_DIGEST_LENGTH分割為n個塊(最後一個分塊可能為不完整分塊):
B0||B1||...||Bn-1=buf,
S0||S1||...||Sn-1=state[state_index,..., state_index+num-1],
n=Ceil(num/MD_DIGEST_LENGTH)。
步驟3:local_md = md。
步驟4:對每個塊i = 0,1,2,...,n-1 執行
-
-
- 4.1 計算雜湊值
-
local_md = HASH (local_md ||Si||Bi||md_count[0]||md_count[1])
-
-
- 4.2 更新Si = Si ⊕ local_md
- 4.3 更新md_count[1] = md_count[1]+1
-
步驟5:用更新的Si,i=0,1,2,...,n-1更新狀態state:
state[state_index,..., state_index+num-1] = S0||S1||...||Sn-1
步驟6:用最後得到的local_md更新md = md ⊕local_md。
步驟7:更新熵值:entropy = min(32, entropy + add)。
圖中的簡寫
C0即md_count[0] |
C1即md_count[1] |
idx即state_index |
函 數: int ssleay_rand_bytes(unsigned char *buf, int num)
功能描述: 產生隨機數
說 明: 無
注 意: 新增資訊的熵值(add)可為小數。
引數說明:
buf (in) 新增資訊
num (in) 新增資訊的位元組長度
add (in) 新增資訊的熵值,單位:位元組。
返 回 值: 無
執行步驟:
步驟1:若未初始化(initialized為0),則執行RAND_poll()增加熵值。
步驟2:計算幾個基本記號
L2 = MD_DIGEST_LENGTH/2,
L1 = MD_DIGEST_LENGTH,
n=Ceil(num/ L2 ),
num_ceil = Ceil (num/ L)* L
步驟3:若熵源池未攪拌(本函式內定義的靜態變數stirred_pool),則執行m = Ceil(STATE_SIZE/ L1) 次新增熵資訊
rand_add("....................",L1, 0.0)。
步驟4:local_md = md。
步驟5:將輸出資料緩衝區buf以及內部狀態中的環形緩衝區state(state從state_index起的num位元組資料),分別按L2分割為n=Ceil(num/ L2 )個塊(最後一個分塊可能為不完整分塊):
B0||B1||...||Bn-1=buf,
S0||S1||...||Sn-1=state[state_index,..., state_index+num-1]。
步驟6:for k = 0,1, 2,...,n-1,執行
-
-
- 6.1 設定字串pad:若k為0,則pad = getpid(),否則,pad = NULL為空串。
- 6.2 計算雜湊值
-
local_md = HASH( pad || local_md || md_count[0] || md_count[1] || Bi || Si)
注意:Bi是輸出緩衝區,但這裡讀取了其中的資料
-
-
- 6.3 更新Si和Bi
-
Si = Si ⊕ local_md[0,..., L2-1]
Bi = Bi ⊕ local_md[L2,..., L1-1]
注意:這裡沒有更新md_count[0]和md_count[1]的步驟。
步驟7:更新md:
md = HASH(md_count[0] || md_count[1]|| local_md || md)
步驟8:用更新的Si,i=0,1,2,...,n-1更新狀態state:
state[state_index,..., state_index+num-1] = S0||S1||...||Sn-1
步驟9:更新state_index =( state_index + num_ceil )mod state_num
步驟10:更新md_count[0] = md_count[0]+ 1;
步驟11:輸出隨機數buf = B0||B1||...||Bn-1
4. 熵採集
熵採集函式為RAND_poll,它在不同作業系統下使用不同的熵源產生方式,Windows下的程式碼見.\crypto\rand\rand_win.c。
此外,不同的OpenSSL版本中其熵採集方式也有較大出入。
OpenSSL版本 |
熵採集 |
v0.9.8a |
使用了大量的各種資訊,詳情見本章後續部分。 |
v1.1.0 |
依賴於Windows提供的隨機數,不再去採集各種資訊。 |
FIPS版本 |
使用者自行提供熵,以回撥函式方式設定,內部不提供熵。 |
作做熵源的資訊包括下列資訊。
函 數: int RAND_poll(void)
功能描述: 採集各種熵源新增到內部狀態
說 明: 此為Windows版本
注 意: -
引數說明: 無
返 回 值: 無
執行步驟:下面主要說明新增的各種資訊
資訊1:NetStatisticsGet,45位元組熵;netstatget,17位元組熵;
NetStatisticsGet() is a Unicode only function, STAT_WORKSTATION_0 contains 45 fields and STAT_SERVER_0 contains 17 fields.
NetStatisticsGet(NULL, L"LanmanWorkstation", 0, 0, &outbuf);
RAND_add(outbuf, sizeof(STAT_WORKSTATION_0), 45);
netstatget(NULL, L"LanmanServer", 0, 0, &outbuf);
RAND_add(outbuf, sizeof(STAT_SERVER_0), 17);
資訊2:Windows API 提供的隨機數,熵值為sizeof(buf)位元組
- 2.a如果BCryptGenRandom api存在,則執行,
BCryptGenRandom(NULL, buf, sizeof(buf), flag)
RAND_add(buf, sizeof(buf), sizeof(buf));
這裡flag = BCRYPT_USE_SYSTEM_PREFERRED_RNG
- 2.b否則,即BCryptGenRandom api不存在,則執行
- 2.b.1 windows的CryptGenRandom,熵值為0位元組
CryptAcquireContextW(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
CryptGenRandom(hProvider, sizeof(buf), buf);
RAND_add(buf, sizeof(buf), 0);
-
- 2.b.2 intel的CryptGenRandom,熵值為sizeof(buf)位元組
CryptAcquireContextW(&hProvider, 0, INTEL_DEF_PROV, PROV_INTEL_SEC, 0))
CryptGenRandom(hProvider, sizeof(buf), buf);
RAND_add(buf, sizeof(buf), sizeof(buf));
資訊3:windows控制代碼,熵值為0位元組
HWND h = GetForegroundWindow();
RAND_add(&h, sizeof(h), 0);
資訊4:滑鼠位置,熵值2位元組
GetCursorInfo(&ci);
RAND_add(&ci, sizeof(CURSORINFO), 2);
資訊5:GetQueueStatus狀態,熵值1位元組
w = GetQueueStatus(QS_ALLEVENTS);
RAND_add(&w, sizeof(w), 1);
資訊6:堆資訊與遍歷,熵值多位元組
HEAPLIST32 contains 3 fields that will change with each entry, Consider each field a source of 1 byte of entropy.
HEAPENTRY32 contains 5 fields that will change with each entry, Consider each field a source of 1 byte of entropy.
handle = CreateToolhelp32Snapshot(TH32CS_SNAPALL,0);
hlist.dwSize = sizeof(HEAPLIST32);
if (good) stoptime = GetTickCount() + MAXDELAY;(為避免時間過長,設定了一個時間閾值)
if (Heap32ListFirst (handle, &hlist)){
do{
RAND_add(&hlist, hlist.dwSize, 3);
hentry.dwSize = sizeof(HEAPENTRY32);
if (Heap32First(&hentry, hlist.th32ProcessID, hlist.th32HeapID)){
int entrycnt = 80;
do{
RAND_add(&hentry, hentry.dwSize, 5);
}while (heap_next(&hentry) && --entrycnt > 0);
}//end if
} while (Heap32ListNext(handle, &hlist) && GetTickCount() < stoptime);
}//end if
資訊7:程序資訊遍歷,熵值9位元組/程序
PROCESSENTRY32 contains 9 fields that will change with each entry. Consider each field a source of 1 byte of entropy.
p.dwSize = sizeof(PROCESSENTRY32);
if (good) stoptime = GetTickCount() + MAXDELAY;(為避免時間過長,設定了一個時間閾值)
if (Process32First(handle, &p)){
do
{
RAND_add(&p, p.dwSize, 9);
}while (Process32Next(handle, &p) && GetTickCount() < stoptime);
}//end if
資訊8:執行緒資訊遍歷,熵值6位元組/執行緒
THREADENTRY32 contains 6 fields that will change with each entry. Consider each field a source of 1 byte of entropy.
t.dwSize = sizeof(THREADENTRY32);
if (good) stoptime = GetTickCount() + MAXDELAY;
if (Thread32First(handle, &t)){
do{
RAND_add(&t, t.dwSize, 6);
}while (Thread32Next(handle, &t) && GetTickCount() < stoptime);
}//end if
資訊9:模組遍歷,熵值9位元組/模組
MODULEENTRY32 contains 9 fields that will change with each entry. Consider each field a source of 1 byte of entropy.
m.dwSize = sizeof(MODULEENTRY32);
if (good) stoptime = GetTickCount() + MAXDELAY;
if (Module32First(handle, &m)){
do
{
RAND_add(&m, m.dwSize, 9);
}while (Module32Next(handle, &m) && (GetTickCount() < stoptime));
}//end if
資訊10:時間資訊,熵值1位元組
呼叫readtimer()獲取時間資訊加入RAND_add,1.1.0版也在用。
1. 優先呼叫 RDTSC,並增加RAND_add(,,1),即熵加1。
2. 上述步驟不可用則呼叫QueryPerformanceCounter,並增加RAND_add(,,0),即熵加0。
3. 上述步驟不可用則呼叫GetTickCount,並增加RAND_add(,,0),即熵加0。
資訊11:記憶體狀態資訊,熵值1位元組
GlobalMemoryStatus Function Retrieves information about the system's current usage of both physical and virtual memory.
GlobalMemoryStatus(&m); /*1.1.0版在用*/
RAND_add(&m, sizeof(m), 1);
資訊12:程序ID,熵值1位元組
w = GetCurrentProcessId();/*1.1.0版在用*/
RAND_add(&w, sizeof(w), 1);
函式:int RAND_poll(void)
使用系統提供的隨機數作為熵源
此外還如下資訊
RAND_add (PID,,0);
RAND_add (UID,,0);
RAND_add (time,,0);