隨機(jī)數(shù)是一種無規(guī)律的數(shù),但是真正做到完全無規(guī)律也較困難,所以一般將它稱之為偽隨機(jī)數(shù)。隨機(jī)數(shù)在密碼學(xué)用的很多,比如SSL握手中的客戶端hello和服務(wù)端hello消息中都有隨機(jī)數(shù);SSL握手中的預(yù)主密鑰是隨機(jī)數(shù);RSA密鑰生成也用到隨機(jī)數(shù)。如果隨機(jī)數(shù)有問題,會帶來很大的安全隱患。軟件生成隨機(jī)數(shù)一般預(yù)先設(shè)置隨機(jī)數(shù)種子,再生成隨機(jī)數(shù)。設(shè)置隨機(jī)數(shù)種子可以說是對生成隨機(jī)數(shù)過程的一種擾亂,讓產(chǎn)生的隨機(jī)數(shù)更加無規(guī)律可循。生成隨機(jī)數(shù)有多種方法,可以是某種算法也可以根據(jù)某種或多種隨機(jī)事件來生成。比如,鼠標(biāo)的位置、系統(tǒng)的當(dāng)前時(shí)間、本進(jìn)程/線程相關(guān)信息以及機(jī)器噪聲等。安全性高的應(yīng)用一般都采用硬件方式(隨機(jī)數(shù)發(fā)生器)來生成隨機(jī)數(shù)。
本文假設(shè)你已經(jīng)安裝好了OpenSSL,并且持有一份1.1.1的源碼。
隨機(jī)數(shù)相關(guān)的頭文件為rand.h、源文件在crypto/rand目錄中。
主要結(jié)構(gòu):
struct rand_meth_st {
int (*seed) (const void *buf, int num);
int (*bytes) (unsigned char *buf, int num);
void (*cleanup) (void);
int (*add) (const void *buf, int num, double randomness);
int (*pseudorand) (unsigned char *buf, int num);
int (*status) (void);
};
typedef struct rand_meth_st RAND_METHOD;
這個(gè)結(jié)構(gòu)定義了涉及隨機(jī)數(shù)生成的抽象方法集合。主要字段含義:
seed —— 隨機(jī)數(shù)種子函數(shù)。
bytes —— 隨機(jī)數(shù)生成函數(shù)。
cleanup —— 狀態(tài)清除函數(shù)。
add —— 隨機(jī)數(shù)種子添加函數(shù)。
pseudorand —— 可重現(xiàn)的隨機(jī)數(shù)函數(shù)。
status —— 狀態(tài)查詢函數(shù)。
在1.1.1中,大多數(shù)的數(shù)據(jù)結(jié)構(gòu)已經(jīng)不再向使用者開放,從封裝的角度來看,這是更合理的。如果你在頭文件中找不到結(jié)構(gòu)定義,不妨去源碼中搜一搜。
主要函數(shù):
int RAND_set_rand_method(const RAND_METHOD *meth);
設(shè)置自定義的隨機(jī)數(shù)抽象方法。
成功返回1,失敗返回0。
const RAND_METHOD *RAND_get_rand_method(void);
獲取當(dāng)前的隨機(jī)數(shù)抽象方法集合。
成功返回有效指針,失敗返回NULL。
在我們未調(diào)用RAND_set_rand_method()的情況下,該函數(shù)返回默認(rèn)的抽象方法集合。
RAND_METHOD *RAND_OpenSSL(void);
這個(gè)函數(shù)返回OpenSSL內(nèi)置的隨機(jī)數(shù),通常為RAND_DRBG隨機(jī)數(shù)。
void RAND_seed(const void *buf, int num);
種子函數(shù),為了讓openssl內(nèi)部維護(hù)的隨機(jī)數(shù)據(jù)更加無序,可使用本函數(shù)。buf為用戶輸入的隨機(jī)數(shù)地址,num為其字節(jié)數(shù)。Openssl將用戶提供的buf中的隨機(jī)內(nèi)容與其內(nèi)部隨機(jī)數(shù)據(jù)進(jìn)行摘要計(jì)算,更新其內(nèi)部隨機(jī)數(shù)據(jù)。
void RAND_add(const void *buf, int num, double randomness);
與seed類似,也是為了讓openssl內(nèi)部隨機(jī)數(shù)據(jù)更加無序,其中entropy(信息熵)可以看作用戶本次加入的隨機(jī)數(shù)的個(gè)數(shù)。從內(nèi)部實(shí)現(xiàn)來看,RAND_seed()相當(dāng)于調(diào)用RAND_add(buf, num, num),此時(shí)傳遞的entropy(信息熵)和緩沖區(qū)長度是一樣的。至于num和randomness這兩個(gè)參數(shù)如何設(shè)置,建議randomness不要超過num的長度,最好是兩者相同,或者直接調(diào)用RAND_seed(),盡量避免RAND_add()的調(diào)用。
int RAND_bytes(unsigned char *buf, int num);
生成隨機(jī)數(shù),openssl根據(jù)內(nèi)部維護(hù)的隨機(jī)數(shù)狀態(tài)來生成結(jié)果。buf用于存放生成的隨機(jī)數(shù)。num為輸入?yún)?shù),用來指明生成隨機(jī)數(shù)的字節(jié)長度。
成功返回1,失敗返回0。
int RAND_status(void);
查看熵值是否達(dá)到預(yù)定值,如果達(dá)到則返回1,否則返回0。
在openssl實(shí)現(xiàn)的md_rand中該函數(shù)會調(diào)用RAND_poll函數(shù)來使熵值合格。如果本函數(shù)返回0,則說明此時(shí)用戶不應(yīng)生成隨機(jī)數(shù),需要調(diào)用seed和add函數(shù)來添加熵值。
從1.1.1版本的使用情況來看,不需要調(diào)用RAND_seed(),RAND_status()總是返回成功的,但是建議使用者從安全考慮,雖然不需要理會RAND_status(),請?jiān)谡{(diào)用RAND_bytes()之前,總是使用RAND_seed()先初使化隨機(jī)種子。
const char *RAND_file_name(char *file, size_t num);
指定file緩沖區(qū)和num長度,生成隨機(jī)的文件路徑,如果num設(shè)置太小不足以容納完整路徑,則返回NULL,建議file緩沖區(qū)通常指定256字節(jié)。
int RAND_load_file(const char *file, long max_bytes);
將file指定的隨機(jī)數(shù)文件中的數(shù)據(jù)讀取bytes字節(jié)(如果bytes大于1024,則讀取1024字節(jié)),內(nèi)部調(diào)用RAND_add進(jìn)行計(jì)算,生成內(nèi)部隨機(jī)數(shù)。
成功返回加載的字節(jié)數(shù)(0表示文件為空),失敗返回-1。
int RAND_write_file(const char *file);
生成一個(gè)隨機(jī)數(shù)文件,返回生成的內(nèi)容大小,通常為1024字節(jié)。
使用舉例:
這個(gè)例子演示了隨機(jī)數(shù)的相關(guān)API操作。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/rand.h>
namespace dakuang {}
int main(int argc, char* argv[])
{
int nRet = RAND_status();
printf("before RAND_seed() RAND_status() ret:[%d] \n", nRet);
char sBuf[20] = {0};
strcpy(sBuf, "1234567890");
RAND_seed(sBuf, 10);
nRet = RAND_status();
printf("RAND_status() ret:[%d] \n", nRet);
unsigned char sBufOut[20] = {0};
nRet = RAND_bytes(sBufOut, 20);
printf("RAND_bytes() ret:[%d] \n", nRet);
for (int i = 0; i < 20; ++i)
{
printf("%02x", sBufOut[i]);
}
printf("\n");
char sFileName[256] = {0};
const char* p = RAND_file_name(sFileName, 256);
printf("p:[%p - %s] sFile:[%p - %s] \n", p, p, sFileName, sFileName);
int nBytesWrite = RAND_write_file(p);
printf("byteswrite:[%d] \n", nBytesWrite);
int nBytesRead = RAND_load_file(p, 512);
printf("bytesread:[%d] \n", nBytesRead);
return 0;
}
輸出:
before RAND_seed() RAND_status() ret:[1]
RAND_status() ret:[1]
RAND_bytes() ret:[1]
04196aa58505fdeef8c5bf9ef9d22e07a3d6859c
p:[0x7ffd028a9070 - /home/test/.rnd] sFile:[0x7ffd028a9070 - /home/test/.rnd]
byteswrite:[1024]
bytesread:[512]