version | date | commit-msg |
---|---|---|
1.0 | 2019/7/30 | first commit |
在平常的开发中,涉及到加解密库的开发总离不开对OpenSSL接口的调用,笔者借对公司内部加解密库进行国密算法扩充的契机,对 OpenSSL进行了一番学习与实践😀。git
所以本文将介绍EVP接口的使用,并给出SM2公钥加密的具体实现。github
OpenSSL提供了一系列的函数用于特定加解密算法,好比,可使用#include "rsa.h"
中的RSA_public_encrypt()
完成RSA公钥加密计算。 可是,OpenSSL还提供了一种统一接口,开发者可经过调用统一接口,使得只须要在初始化参数的时候作不多的改变,就可使用相同的代码但采用不一样的加密算法进行数据的加密和解密[1],这种统一接口就是本文要介绍的EVP接口。算法
EVP接口封装了摘要算法,密钥生成,对称加解密,非对称加解密,验证等功能,其中比较常见的函数有:函数
EVP接口提供了对称加解密函数,其中有专门进行加密的函数,在进行加密以前,首先初始化加密上下文,而后调用加密函数进行加密计算。学习
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
复制代码
#include "evp.h"
/* * @param ctx: 这表明加解密的上下文 * @param type: type表明须要使用的算法类型。 好比填写EVP_sm4_cbc()则使用了CBC分组模式的SM4算法。 在evp中预先定义了多种type可供开发者选用 * @param impl:ENGINE表明了加解密的引擎。 OpenSSL是支持自定义加解密引擎的,即自定义加解密算法的具体实现而 不使用默认的OpenSSL实现。 通常状况下,咱们都使用OpenSSL中的加解密算法,所以可置为NULL * @param key: 密钥 * @param iv: 初始向量,若是选择了ECB,则不须要iv */
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
EVP_EncryptUpdate()
EVP_EncryptFinal_ex()
复制代码
#include "evp.h"
/* * @param out: 密文 * @param outl: 密文长度 * @param in: 明文 * @param inl: 明文长度 */
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
复制代码
#include "evp.h"
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
复制代码
EVP提供了对应的解密函数,参数设置与加密函数相似,用法也很相似,接口以下:加密
#include "evp.h"
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
复制代码
EVP更是提供了加密和解密的统一接口,该接口惟一的区别就是在初始化的时候,经过enc参数断定是加密模式仍是解密模式。spa
#include "evp.h"
/* * @param enc: enc==1表明加密,enc==0表明解密 */
int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc);
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
复制代码
进行公钥加密的重点在于设置密钥类型。在OpenSSL中,存在多种类型的密钥和一个统一的EVP_PKEY
密钥,须要经过辅助函数将特定密钥转化成统一密钥。.net
特定密钥有:code
ec.h
头文件中的EC_KEY
表明了椭圆曲线的密钥rsa.h
头文件中的RSA
表明RSA密钥DH
密钥和DSA
密钥等这些特定的密钥可使用辅助函数进行设置:htm
int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, EC_KEY *key);
将EC_KEY
设置到EVP_PKEY
中int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key);
将RSA
设置到EVP_PKEY
中在OpenSSL1.0.*
版本中,外部是能够引用#include "sm2.h"
的。🤣那是一个美好的田园时光,网上大量的对sm2算法的调用都经过 sm2.h
头文件中定义的sm2_*
函数进行。当时笔者须要对公共加密库进行sm2扩充的时候,苦苦搜寻sm2.h
而不得,最后才发如今OpenSSL1.1*
版本已经再也不对外暴露sm2.h
,所以在OpenSSL1.1*
中只能彻底经过 evp.h
调用了。
首先是密钥的生成,在生成密钥阶段,就须要告诉OpenSSL生成SM2密钥。
EC_KEY* key = EC_KEY_new();
/* * OpenSSL内置了许多曲线,所以须要设置使用哪条曲线。NID_sm2 */
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(key, group);
// 根据曲线,生成密钥
EC_KEY_generate_key(key);
// 可使用该函数对生成的密钥进行检查
EC_KEY_check_key((const EC_KEY*)key)
复制代码
为完成非对称公钥加密,须要依次调用如下这些函数:
// 初始化公钥上下文
1. EVP_PKEY *p_key = EVP_PKEY_new();
// 将EC_KEY结构中存储的密钥保存到EVP_PKEY中,并设置EVP_PKEY的类型为椭圆曲线密钥
2. EVP_PKEY_set1_EC_KEY(p_key, key);
// 设置密钥类型为SM2密钥,而非其余的什么密钥
3. EVP_PKEY_set_alias_type(p_key, EVP_PKEY_SM2);
// 初始化加密上下文,须要传入密钥。 第二个参数为ENGINE,同上,可置为NULL
4. EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(p_key, NULL);
// 初始化加密上下文
5. EVP_PKEY_encrypt_init(ctx);
// 使用公钥进行加密
6. EVP_PKEY_encrypt(ctx, *ciphertext, ciphertext_len, plaintext, plain_len);
复制代码
以上步骤比较重要的是第3步。OpenSSL内置了一些椭圆曲线,所以须要开发者显示的指定使用哪条曲线,所以在密钥中指定使用SM2,最终会在初始化加密上下文的时候,告诉OpenSSL具体的算法,从而在加密阶段使用SM2算法。
摘要函数的调用须要使用到4个函数便可:
// 初始化计算上下文
1. EVP_MD_CTX* ctx = EVP_MD_CTX_new();
// 初始化摘要计算,其中type表明须要使用的摘要算法。好比EVP_sm3()使用sm3摘要
2. int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
3. int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
4. int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
复制代码