OpenSSL之内存用法

Posted

tags:

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

参考技术A

用户在使用内存时,容易犯的错误就是内存泄露。当用户调用内存分配和释放函数时,查找内存泄露比较麻烦。OpenSSL提供了内置的内存分配/释放函数。如果用户完全调用OpenSSL的内存分配和释放函数,可以方便的找到内存泄露点。OpenSSL分配内存时,在其内部维护一个内存分配哈希表,用于存放已经分配但未释放的内存信息。当用户申请内存分配时,在哈希表中添加此项信息,内存释放时删除该信息。当用户通过OpenSSL函数查找内存泄露点时,只需查询该哈希表即可。用户通过OpenSSL回调函数还能处理那些泄露的内存。

本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
内存相关的头文件为crypto.h、源文件在crypto目录中,文件名模式为mem*.c。

这个结构定义了内存块的分配信息。主要字段含义:
addr —— 分配的内存地址。
num —— 分配的内存大小。
file —— 分配内存的文件名。
file —— 分配内存的行号。
threadid —— 分配内存的线程ID。

在1.1.1中,大多数的数据结构已经不再向使用者开放,从封装的角度来看,这是更合理的。如果你在头文件中找不到结构定义,不妨去源码中搜一搜。

int CRYPTO_set_mem_functions(
void ( m) (size_t, const char *, int),
void ( r) (void *, size_t, const char , int),
void (
f) (void *, const char , int));
成功返回1,失败返回0。
void CRYPTO_get_mem_functions(
void (
m) (size_t, const char , int),
void (
r) (void , size_t, const char , int),
void (
f) (void *, const char *, int));
这两个函数用于设置和读取与内存分配和释放相关的三个函数,这三个函数默认分别为:
static void ( malloc_impl)(size_t, const char *, int) = CRYPTO_malloc;
static void ( realloc_impl)(void *, size_t, const char , int) = CRYPTO_realloc;
static void (
free_impl)(void *, const char *, int) = CRYPTO_free;
这两个函数只允许在内存分配前即初使化时调用。

int CRYPTO_set_mem_debug(int flag);
设置是否开启内存调试。取值1或0,1表示打开。
成功返回1,失败返回0。
只允许在内存分配前即初使化时调用。

void *CRYPTO_malloc(size_t num, const char *file, int line);
void *CRYPTO_zalloc(size_t num, const char *file, int line);
void *CRYPTO_realloc(void *addr, size_t num, const char *file, int line);
CRYPTO_free(void *ptr, const char *file, int line);
这几个函数用于分配和释放内存。
是否记录内存与CRYPTO_set_mem_debug()的开关有关。

int CRYPTO_mem_ctrl(int mode);
这个函数用于设置是否开启内存记录模式,参数取值为:
# define CRYPTO_MEM_CHECK_OFF 0x0
# define CRYPTO_MEM_CHECK_ON 0x1
返回旧模式。
注:我没有在头文件中看到像老版函数CRYPTO_is_mem_check_on()一样可以查询当前开关状态的用法,但是通过查看源码,发现可以对CRYPTO_mem_ctrl()传递一个不存在的类型(比如-1)使其返回当前状态。

int CRYPTO_mem_leaks_cb(int (*cb) (const char *str, size_t len, void *u), void *u);
遍历记录的内存,用于打印内存泄露信息。
返回是否有内存汇露。分别取值1和0。

int CRYPTO_mem_leaks(BIO *bio);
打印内存泄露信息,输出到BIO中。
返回是否有内存汇露。分别取值1和0。
int CRYPTO_mem_leaks_fp(FILE *);
CRYPTO_mem_leaks()的FILE版本。

由于OpenSSL内部涉及地数据结构众多,如果采用原始的内存操作函数,因为涉及到文件名和行号会比较麻烦。因此,OpenSSL定义了一组宏进行包装,方便使用者进行调用。定义如下:

下面这个例子演示了使用OpenSSl内存分配API进行操作。同时还用到了placement new()的用法,演示如何在openssl的内存分配串上创建c++对象。

如果在编译过程中出现下面这个错误:
testmem.cpp:40:42: 错误:‘CRYPTO_mem_leaks_cb’在此作用域中尚未声明
CRYPTO_mem_leaks_cb(memleak_cb, "aaa");
解决办法是开启debug调试编译,命令如下:
./config --prefix=/usr/local/openssl enable-crypto-mdebug enable-crypto-mdebug-backtrace

输出:
old switch:0
@SNode::SNode name:[abc] age:[18]
@SNode::~SNode name:[abc] age:[0]
leak info:[[02:21:40] 1 file=testmem.cpp, line=46, thread=140607514695488, number=36, address=0x204f090
]
##> ./testmem() [0x4044e3]
##> ./testmem(CRYPTO_malloc+0xa3) [0x4035e3]
##> ./testmem() [0x40329c]
##> /lib64/libc.so.6(__libc_start_main+0xf5) [0x7fe1bbb77555]
##> ./testmem() [0x4030f3]
leak info:[36 bytes leaked in 1 chunks
]

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之内存用法的主要内容,如果未能解决你的问题,请参考以下文章

OpenSSL之EVP用法

Linux程序:--OpenSSL库之相关数据结构和内存分配

OpenSSL之AES用法

OpenSSL之3DES用法

OpenSSL之RSA用法

OpenSSL之消息认证码HMAC用法