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的功能过于强大,再加上我的精力和水平有限,暂时只对部分功能进行摘录和说明。
摘要相关的主要数据结构和函数:
struct evp_md_st {
int type;
int pkey_type;
int md_size;
unsigned long flags;
int (*init) (EVP_MD_CTX *ctx);
int (*update) (EVP_MD_CTX *ctx, const void *data, size_t count);
int (*final) (EVP_MD_CTX *ctx, unsigned char *md);
int (*copy) (EVP_MD_CTX *to, const EVP_MD_CTX *from);
int (*cleanup) (EVP_MD_CTX *ctx);
int block_size;
int ctx_size; /* how big does the ctx->md_data need to be */
/* control function */
int (*md_ctrl) (EVP_MD_CTX *ctx, int cmd, int p1, void *p2);
} /* EVP_MD */ ;
typedef struct evp_md_st EVP_MD;
这个结构定义了摘要算法的抽象方法。主要字段含义:
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()来说,其返回值为:
static const EVP_MD md5_md = {
NID_md5,
NID_md5WithRSAEncryption,
MD5_DIGEST_LENGTH,
0,
init,
update,
final,
NULL,
NULL,
MD5_CBLOCK,
sizeof(EVP_MD *) + sizeof(MD5_CTX),
};
下面这几个函数查询md的属性信息:
int EVP_MD_type(const EVP_MD *md);
# define EVP_MD_nid(e) EVP_MD_type(e)
# define EVP_MD_name(e) OBJ_nid2sn(EVP_MD_nid(e))
int EVP_MD_pkey_type(const EVP_MD *md);
int EVP_MD_size(const EVP_MD *md);
int EVP_MD_block_size(const EVP_MD *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()函数中根据口令生成密钥时,有使用到这个函数。
非对称加密相关的主要数据结构和函数:
struct evp_pkey_st {
int type;
int save_type;
CRYPTO_REF_COUNT references;
const EVP_PKEY_ASN1_METHOD *ameth;
ENGINE *engine;
ENGINE *pmeth_engine; /* If not NULL public key ENGINE to use */
union {
void *ptr;
# ifndef OPENSSL_NO_RSA
struct rsa_st *rsa; /* RSA */
# endif
# ifndef OPENSSL_NO_DSA
struct dsa_st *dsa; /* DSA */
# endif
# ifndef OPENSSL_NO_DH
struct dh_st *dh; /* DH */
# endif
# ifndef OPENSSL_NO_EC
struct ec_key_st *ec; /* ECC */
ECX_KEY *ecx; /* X25519, X448, Ed25519, Ed448 */
# endif
} pkey;
int save_parameters;
STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */
CRYPTO_RWLOCK *lock;
} /* EVP_PKEY */ ;
struct evp_pkey_st EVP_PKEY;
这个结构定义了非对称密钥信息的存储容器。主要字段含义:
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_PKEY_assign_RSA(pkey,rsa) EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\
(char *)(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。
使用举例1:
下面这个例子演示了使用MD5的两种方法进行摘要计算的过程。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/evp.h>
namespace dakuang {}
void printHex(const unsigned char* pBuf, int nLen)
{
for (int i = 0; i < nLen; ++i)
{
printf("%02x", pBuf[i]);
}
printf("\n");
}
int main(int argc, char* argv[])
{
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
char sText[] = "abdefg1234567890";
const EVP_MD* md = EVP_md5();
int ret = EVP_DigestInit(ctx, md);
printf("EVP_DigestInit() ret:[%d] \n", ret);
ret = EVP_DigestUpdate(ctx, (const void*)sText, strlen(sText));
printf("EVP_DigestUpdate() ret:[%d] \n", ret);
unsigned char sMD5[16] = {0};
unsigned int nMD5Len = 0;
ret = EVP_DigestFinal(ctx, sMD5, &nMD5Len);
printf("EVP_DigestFinal() ret:[%d] \n", ret);
if (ret == 1)
{
printHex((const unsigned char*)sMD5, nMD5Len);
}
EVP_MD_CTX_free(ctx);
ret = EVP_Digest((const void*)sText, strlen(sText), sMD5, &nMD5Len, EVP_md5(), NULL);
printf("EVP_Digest() ret:%d \n", ret);
if (ret == 1)
{
printHex((const unsigned char*)sMD5, nMD5Len);
}
return 0;
}
输出:
EVP_DigestInit() ret:[1]
EVP_DigestUpdate() ret:[1]
EVP_DigestFinal() ret:[1]
e380e88e8d09ebf8d8659a15b0ea70b5
EVP_Digest() ret:1
e380e88e8d09ebf8d8659a15b0ea70b5
使用举例2:
下面这个例子演示了使用DES进行加解密的过程。为了方便程序实现,破例使用了std::string。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/evp.h>
#include <string>
namespace dakuang {}
int main(int argc, char* argv[])
{
unsigned char sKey[] = "12345678";
unsigned char sIV[] = "12345678";
char sText[] = "abcdefghijklmnopqrstuvwxyz";
std::string strCipher;
{
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
const EVP_CIPHER* cipher = EVP_des_ecb();
int ret = EVP_EncryptInit(ctx, cipher, sKey, sIV);
printf("EVP_EncryptInit() ret:[%d] \n", ret);
char sCipher[128] = {0};
int nCipherLen = 128;
ret = EVP_EncryptUpdate(ctx, (unsigned char*)sCipher, &nCipherLen, (const unsigned char*)sText, strlen(sText));
printf("EVP_EncryptUpdate() ret:[%d] \n", ret);
printf("nCipherLen:[%d] \n", nCipherLen);
if (nCipherLen > 0)
{
strCipher.append(sCipher, nCipherLen);
}
ret = EVP_EncryptFinal(ctx, (unsigned char*)sCipher, &nCipherLen);
printf("EVP_EncryptFinal() ret:[%d] \n", ret);
printf("nCipherLen:[%d] \n", nCipherLen);
if (nCipherLen > 0)
{
strCipher.append(sCipher, nCipherLen);
}
printf("cipher size:[%d] \n", strCipher.size());
EVP_CIPHER_CTX_free(ctx);
}
{
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
const EVP_CIPHER* cipher = EVP_des_ecb();
int ret = EVP_DecryptInit(ctx, cipher, sKey, sIV);
printf("EVP_DecryptInit() ret:[%d] \n", ret);
char* pText = (char*)malloc(strCipher.size());;
int nTextLen = strCipher.size();
ret = EVP_DecryptUpdate(ctx, (unsigned char*)pText, &nTextLen, (const unsigned char*)strCipher.data(), strCipher.size());
printf("EVP_DecryptUpdate() ret:[%d] \n", ret);
printf("nTextLen:[%d] \n", nTextLen);
std::string strText;
if (nTextLen > 0)
{
strText.append(pText, nTextLen);
}
ret = EVP_DecryptFinal(ctx, (unsigned char*)pText, &nTextLen);
printf("EVP_DecryptFinal() ret:[%d] \n", ret);
printf("nTextLen:[%d] \n", nTextLen);
if (nTextLen > 0)
{
strText.append(pText, nTextLen);
}
printf("text size:[%d] body:[%s] \n", strText.size(), strText.data());
free(pText);
EVP_CIPHER_CTX_free(ctx);
}
return 0;
}
输出:
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]
使用举例3:
下面这个例子演示了使用SHA1进行RSA签名和验签计算的过程。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
namespace dakuang {}
int main(int argc, char* argv[])
{
ERR_load_RSA_strings();
RSA* rsa = RSA_new();
BIGNUM* bne = BN_new();
BN_set_word(bne, RSA_3);
int ret = RSA_generate_key_ex(rsa, 512, bne, NULL);
printf("RSA_generate_key_ex() ret:[%d] \n", ret);
BN_free(bne);
EVP_PKEY* pkey = EVP_PKEY_new();
ret = EVP_PKEY_assign_RSA(pkey, rsa);
printf("EVP_PKEY_assign_RSA() ret:[%d] \n", ret);
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
char sData[] = "1234567890";
ret = EVP_SignInit(md_ctx, EVP_sha1());
printf("EVP_SignInit() ret:[%d] \n", ret);
ret = EVP_SignUpdate(md_ctx, sData, strlen(sData));
printf("EVP_SignUpdate() ret:[%d] \n", ret);
unsigned char sSha1[64] = {0};
unsigned int nSha1Len = 0;
ret = EVP_SignFinal(md_ctx, sSha1, &nSha1Len, pkey);
printf("EVP_SignFinal() ret:[%d] \n", ret);
printf("sha1 len:[%d] \n", nSha1Len);
EVP_MD_CTX* md_ctx2 = EVP_MD_CTX_new();
ret = EVP_VerifyInit(md_ctx2, EVP_sha1());
printf("EVP_VerifyInit() ret:[%d] \n", ret);
ret = EVP_VerifyUpdate(md_ctx2, sData, strlen(sData));
printf("EVP_VerifyUpdate() ret:[%d] \n", ret);
ret = EVP_VerifyFinal(md_ctx2, sSha1, nSha1Len, pkey);
printf("EVP_VerifyFinal() ret:[%d] \n", ret);
EVP_MD_CTX_free(md_ctx);
EVP_MD_CTX_free(md_ctx2);
EVP_PKEY_free(pkey);
return 0;
}
输出:
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]