随机数是一种无规律的数,但是真正做到完全无规律也较困难,所以一般将它称之为伪随机数。随机数在密码学用的很多,比如SSL握手中的客户端hello和服务端hello消息中都有随机数;SSL握手中的预主密钥是随机数;RSA密钥生成也用到随机数。如果随机数有问题,会带来很大的安全隐患。软件生成随机数一般预先设置随机数种子,再生成随机数。设置随机数种子可以说是对生成随机数过程的一种扰乱,让产生的随机数更加无规律可循。生成随机数有多种方法,可以是某种算法也可以根据某种或多种随机事件来生成。比如,鼠标的位置、系统的当前时间、本进程/线程相关信息以及机器噪声等。安全性高的应用一般都采用硬件方式(随机数发生器)来生成随机数。
本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
随机数相关的头文件为rand.h、源文件在crypto/rand目录中。
主要结构:
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;
这个结构定义了涉及随机数生成的抽象方法集合。主要字段含义:
seed —— 随机数种子函数。
bytes —— 随机数生成函数。
cleanup —— 状态清除函数。
add —— 随机数种子添加函数。
pseudorand —— 可重现的随机数函数。
status —— 状态查询函数。
在1.1.1中,大多数的数据结构已经不再向使用者开放,从封装的角度来看,这是更合理的。如果你在头文件中找不到结构定义,不妨去源码中搜一搜。
主要函数:
int RAND_set_rand_method(const RAND_METHOD *meth);
设置自定义的随机数抽象方法。
成功返回1,失败返回0。
const RAND_METHOD *RAND_get_rand_method(void);
获取当前的随机数抽象方法集合。
成功返回有效指针,失败返回NULL。
在我们未调用RAND_set_rand_method()的情况下,该函数返回默认的抽象方法集合。
RAND_METHOD *RAND_OpenSSL(void);
这个函数返回OpenSSL内置的随机数,通常为RAND_DRBG随机数。
void RAND_seed(const void *buf, int num);
种子函数,为了让openssl内部维护的随机数据更加无序,可使用本函数。buf为用户输入的随机数地址,num为其字节数。Openssl将用户提供的buf中的随机内容与其内部随机数据进行摘要计算,更新其内部随机数据。
void RAND_add(const void *buf, int num, double randomness);
与seed类似,也是为了让openssl内部随机数据更加无序,其中entropy(信息熵)可以看作用户本次加入的随机数的个数。从内部实现来看,RAND_seed()相当于调用RAND_add(buf, num, num),此时传递的entropy(信息熵)和缓冲区长度是一样的。至于num和randomness这两个参数如何设置,建议randomness不要超过num的长度,最好是两者相同,或者直接调用RAND_seed(),尽量避免RAND_add()的调用。
int RAND_bytes(unsigned char *buf, int num);
生成随机数,openssl根据内部维护的随机数状态来生成结果。buf用于存放生成的随机数。num为输入参数,用来指明生成随机数的字节长度。
成功返回1,失败返回0。
int RAND_status(void);
查看熵值是否达到预定值,如果达到则返回1,否则返回0。
在openssl实现的md_rand中该函数会调用RAND_poll函数来使熵值合格。如果本函数返回0,则说明此时用户不应生成随机数,需要调用seed和add函数来添加熵值。
从1.1.1版本的使用情况来看,不需要调用RAND_seed(),RAND_status()总是返回成功的,但是建议使用者从安全考虑,虽然不需要理会RAND_status(),请在调用RAND_bytes()之前,总是使用RAND_seed()先初使化随机种子。
const char *RAND_file_name(char *file, size_t num);
指定file缓冲区和num长度,生成随机的文件路径,如果num设置太小不足以容纳完整路径,则返回NULL,建议file缓冲区通常指定256字节。
int RAND_load_file(const char *file, long max_bytes);
将file指定的随机数文件中的数据读取bytes字节(如果bytes大于1024,则读取1024字节),内部调用RAND_add进行计算,生成内部随机数。
成功返回加载的字节数(0表示文件为空),失败返回-1。
int RAND_write_file(const char *file);
生成一个随机数文件,返回生成的内容大小,通常为1024字节。
使用举例:
这个例子演示了随机数的相关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]