OpenSSL之随机数用法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenSSL之随机数用法相关的知识,希望对你有一定的参考价值。

参考技术A 随机数是一种无规律的数,但是真正做到完全无规律也较困难,所以一般将它称之为伪随机数。随机数在密码学用的很多,比如SSL握手中的客户端hello和服务端hello消息中都有随机数;SSL握手中的预主密钥是随机数;RSA密钥生成也用到随机数。如果随机数有问题,会带来很大的安全隐患。软件生成随机数一般预先设置随机数种子,再生成随机数。设置随机数种子可以说是对生成随机数过程的一种扰乱,让产生的随机数更加无规律可循。生成随机数有多种方法,可以是某种算法也可以根据某种或多种随机事件来生成。比如,鼠标的位置、系统的当前时间、本进程/线程相关信息以及机器噪声等。安全性高的应用一般都采用硬件方式(随机数发生器)来生成随机数。

本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
随机数相关的头文件为rand.h、源文件在crypto/rand目录中。

这个结构定义了涉及随机数生成的抽象方法集合。主要字段含义:
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操作。

输出:
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]

OpenSSL之EVP用法

参考技术A

OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中的各种函数。OpenSSL中实现了各种对称算法、摘要算法以及签名/验签算法。EVP函数将这些具体的算法进行了封装。
EVP主要封装了如下功能函数:
1)实现了BASE64编解码BIO;
2)实现了加解密BIO;
3)实现了摘要BIO;
4)实现了reliable BIO;
5)封装了摘要算法;
6)封装了对称加解密算法;
7)封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
8)基于口令的加密(PBE);
9)对称密钥处理;
10)数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密。发送给对方时,同时发送对称密钥密文和数据密文。接收方首先用自己的私钥解密密钥密文,得到对称密钥,然后用它解密数据。
11)其他辅助函数。

本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
EVP相关的头文件在evp.h中、源文件在crypto/evp目录中。
由于EVP的功能过于强大,再加上我的精力和水平有限,暂时只对部分功能进行摘录和说明。

这个结构定义了摘要算法的抽象方法。主要字段含义:
type —— 摘要算法的NID。
pkey_type —— 与摘要算法相关的密钥NID。
md_size —— 摘要值的输出大小。
flags —— 内部标志。
init —— 初使化函数。
update —— 输入计算函数。
final —— 输出计算函数。
copy —— 摘要运算上下文复制函数。
cleanup —— 摘要运算上下文清理函数。
block_size —— 摘要运算分组大小。
ctx_size —— 摘要运算分组缓冲区大小。
md_ctrl —— 摘要运算指令控制函数。

支持的摘要算法包括:
const EVP_MD *EVP_md5(void);
const EVP_MD *EVP_sha1(void);
const EVP_MD *EVP_sha256(void);
const EVP_MD *EVP_sha512(void);

拿EVP_md5()来说,其返回值为:

下面这几个函数查询md的属性信息:

有时我们对使用的摘要算法不熟悉,这几个函数很有帮助。

EVP_MD_CTX *EVP_MD_CTX_new(void);
void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
这两个函数用于创建和释放对称摘要上下文对象。

int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type);
初使化摘要上下文,type为摘要算法抽象集合。
成功返回1,失败返回0。

int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
向摘要计算的海棉结构输入一段数据。
成功返回1,失败返回0。

int EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
生成最终摘要,输出摘要值和长度。
成功返回1,失败返回0。

int EVP_Digest(const void *data, size_t count, unsigned char *md, unsigned int *size, const EVP_MD *type, ENGINE *impl);
使用包装的一次性方法计算一段小数据的摘要。
成功返回1,失败返回0。

struct evp_cipher_st
int nid;
int block_size;
/* Default value for variable length ciphers /
int key_len;
int iv_len;
/
Various flags /
unsigned long flags;
/
init key /
int (
init) (EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char iv, int enc);
/
encrypt/decrypt data /
int (
do_cipher) (EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char in, size_t inl);
/
cleanup ctx /
int (
cleanup) (EVP_CIPHER_CTX );
/
how big ctx->cipher_data needs to be /
int ctx_size;
/
Populate a ASN1_TYPE with parameters /
int (
set_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE );
/
Get parameters from a ASN1_TYPE /
int (
get_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE );
/
Miscellaneous operations /
int (
ctrl) (EVP_CIPHER_CTX *, int type, int arg, void ptr);
/
Application data */
void app_data;
/
EVP_CIPHER */ ;
typedef struct evp_cipher_st EVP_CIPHER;
这个结构定义了对称加密算法的抽象方法。主要字段含义:
nid —— 加密算法的NID。
block_size —— 分组大小。
key_len —— 密钥长度。
iv_len —— 初使向量长度。
flags —— 内部标志。
init —— 初使化函数。
do_cipher —— 中间运算函数。
cleanup —— 最终运算函数。
ctx_size —— 上下文大小。
ctrl —— 控制函数。
app_data —— 应用程序数据。

支持的CIPHER抽象加解密算法包括:
const EVP_CIPHER *EVP_des_ecb(void);
const EVP_CIPHER *EVP_des_ede3(void);
const EVP_CIPHER *EVP_aes_128_ecb(void);
const EVP_CIPHER *EVP_aes_128_cbc(void);

下面这几个函数查询cipher的属性信息:
int EVP_CIPHER_nid(const EVP_CIPHER *cipher);
int EVP_CIPHER_type(const EVP_CIPHER *ctx);
# define EVP_CIPHER_name(e) OBJ_nid2sn(EVP_CIPHER_nid(e))
int EVP_CIPHER_block_size(const EVP_CIPHER *cipher);
int EVP_CIPHER_key_length(const EVP_CIPHER *cipher);
int EVP_CIPHER_iv_length(const EVP_CIPHER *cipher);
有时我们对使用的加密算法不熟悉,这几个函数很有帮助。

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *c);
这两个函数用于创建和释放对称加解密上下文对象。

int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen);
当对称算法密钥长度为可变长时,设置对称算法的密钥长度。
成功返回1,失败返回0。

int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad);
设置对称算法的填充,对称算法有时候会涉及填充。
pad取值0和1,当pad为1时表示使用填充。默认的填充策略采用PKCS5规范,即最后一个分组被填充n个字节时,其填充值均为n。
成功返回1,失败返回0。

int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, const unsigned char *key, const unsigned char *iv);
初使化对称加密上下文。
成功返加1,失败返回0。

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
加密一段明文。
成功返加1,失败返回0。成功时,outl输出密文长度。

int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
加密余下的明文。
成功返加1,失败返回0。成功时,outl输出密文长度。

int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, const unsigned char *key, const unsigned char *iv);
初使化对称解密上下文。
成功返加1,失败返回0。

int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
解密一段密文。
成功返加1,失败返回0。成功时,outl输出明文长度。

int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
解密余下的密文。
成功返加1,失败返回0。成功时,outl输出明文长度。

int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md,
const unsigned char *salt,
const unsigned char *data, int datal, int count,
unsigned char *key, unsigned char *iv);
计算密钥函数,它根据算法类型、摘要算法、salt以及输入数据计算出一个对称密钥和初始化向量iv。返加密钥的长度。
在PEM_do_header()函数中根据口令生成密钥时,有使用到这个函数。

这个结构定义了非对称密钥信息的存储容器。主要字段含义:
type —— 非对称加密算法的NID。
save_type —— 保存的PKEY类型。
pkey —— 保存的PKEY指针,如RSA结构指针。

EVP_PKEY *EVP_PKEY_new(void);
void EVP_PKEY_free(EVP_PKEY *pkey);
这两个函数用于创建和释放PKEY上下文对象。

int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key);
为PKEY关联指定算法类型的上下文结构,如为RSA关联的宏定义如下:

# define EVP_SignInit(a,b) EVP_DigestInit(a,b)
# define EVP_SignUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
int EVP_SignFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s,
EVP_PKEY *pkey);
签名计算。从宏定义可以看出实际上就是先计算摘要,再用RSA私钥加密。
成功返加1,失败返回0。

# define EVP_VerifyInit(a,b) EVP_DigestInit(a,b)
# define EVP_VerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
int EVP_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sigbuf,
unsigned int siglen, EVP_PKEY *pkey);
验签计算。从宏定义可以看出实际上就是先计算摘要,再用RSA公钥解密签名,再与摘要进行比对。
成功返加1,失败返回0。

下面这个例子演示了使用MD5的两种方法进行摘要计算的过程。

输出:
EVP_DigestInit() ret:[1]
EVP_DigestUpdate() ret:[1]
EVP_DigestFinal() ret:[1]
e380e88e8d09ebf8d8659a15b0ea70b5
EVP_Digest() ret:1
e380e88e8d09ebf8d8659a15b0ea70b5

下面这个例子演示了使用DES进行加解密的过程。为了方便程序实现,破例使用了std::string。

输出:
EVP_EncryptInit() ret:[1]
EVP_EncryptUpdate() ret:[1]
nCipherLen:[24]
EVP_EncryptFinal() ret:[1]
nCipherLen:[8]
cipher size:[32]
EVP_DecryptInit() ret:[1]
EVP_DecryptUpdate() ret:[1]
nTextLen:[24]
EVP_DecryptFinal() ret:[1]
nTextLen:[2]
text size:[26] body:[abcdefghijklmnopqrstuvwxyz]

下面这个例子演示了使用SHA1进行RSA签名和验签计算的过程。

输出:
RSA_generate_key_ex() ret:[1]
EVP_PKEY_assign_RSA() ret:[1]
EVP_SignInit() ret:[1]
EVP_SignUpdate() ret:[1]
EVP_SignFinal() ret:[1]
sha1 len:[64]
EVP_VerifyInit() ret:[1]
EVP_VerifyUpdate() ret:[1]
EVP_VerifyFinal() ret:[1]

以上是关于OpenSSL之随机数用法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Linux命令生成随机密码?

php源码分析之DZX1.5随机数函数random用法

Go语言之for的五种用法和生成99这个随机数

如何在 openssl 中修复“无法写入‘随机状态’”

知识点:Mysql 基本用法之函数

OpenSSL 在 PKCS12 导出期间挂起,并显示“正在将‘屏幕’加载到随机状态”