openssl+EVP详解
EVP(Enveloped Public Key)是 OpenSSL 中用于提供对称加密、非对称加密和哈希功能的高级加密接口。EVP 库提供了一个抽象的加密框架,使得可以在不同的算法实现之间进行切换,而不需要改变应用程序的代码。以下是一些 EVP 开发的主要方面:
一、EVP基本介绍
1. EVP 加密和解密
EVP 提供了通用的加密和解密函数,可以用于对称加密和非对称加密。一般的流程如下:
- 选择加密算法,创建相应的
EVP_CIPHER
结构。 - 初始化
EVP_CIPHER_CTX
上下文。 - 使用
EVP_EncryptInit_ex
或EVP_DecryptInit_ex
初始化加密或解密操作。 - 使用
EVP_EncryptUpdate
或EVP_DecryptUpdate
处理数据。 - 使用
EVP_EncryptFinal_ex
或EVP_DecryptFinal_ex
完成加密或解密操作。
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, plaintext_len);
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &tmplen);
EVP_CIPHER_CTX_free(ctx);
2. EVP 签名和验证
EVP 提供了通用的签名和验证函数,可以用于不同的哈希算法和签名算法。一般的流程如下:
- 选择哈希算法和签名算法,创建相应的
EVP_MD
和EVP_PKEY
结构。 - 初始化
EVP_MD_CTX
上下文。 - 使用
EVP_DigestInit_ex
初始化哈希操作。 - 使用
EVP_DigestUpdate
处理数据。 - 使用
EVP_DigestFinal_ex
完成哈希操作。 - 使用
EVP_SignInit_ex
或EVP_VerifyInit_ex
初始化签名或验证操作。 - 使用
EVP_SignUpdate
或EVP_VerifyUpdate
处理数据。 - 使用
EVP_SignFinal
或EVP_VerifyFinal
完成签名或验证操作。
EVP_MD_CTX *mdctx;
const EVP_MD *md = EVP_sha256();
EVP_PKEY *pkey;
unsigned char signature[256];
size_t sig_len;
mdctx = EVP_MD_CTX_new();
pkey = load_private_key();
EVP_DigestInit_ex(mdctx, md, NULL);
EVP_DigestUpdate(mdctx, data, data_len);
EVP_DigestFinal_ex(mdctx, digest, &digest_len);
EVP_SignInit_ex(mdctx, EVP_sha256(), NULL);
EVP_SignUpdate(mdctx, data, data_len);
EVP_SignFinal(mdctx, signature, &sig_len, pkey);
EVP_MD_CTX_free(mdctx);
3. EVP 加解密文件
EVP 还提供了一些便捷的函数用于对文件进行加解密操作,例如 EVP_EncryptInit_ex
、EVP_DecryptUpdate
、EVP_EncryptFinal_ex
等。
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, plaintext_len);
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &tmplen);
EVP_CIPHER_CTX_free(ctx);
这些是 EVP 库的一些基本用法。具体的使用取决于你的需求,可以根据 OpenSSL 的文档和示例代码进一步了解。
二、源码结构
evp源码位于crypto/evp目录,可以分为如下几类:
2.1 全局函数
主要包括c_allc.c、c_alld.c、c_all.c以及names.c。他们加载openssl支持的所有的对称算法和摘要算法,放入到哈希表中。实现了OpenSSL_add_all_digests、OpenSSL_add_all_ciphers以及OpenSSL_add_all_algorithms(调用了前两个函数)函数。在进行计算时,用户也可以单独加载摘要函数(EVP_add_digest)和对称计算函数(EVP_add_cipher)。
2.2 BIO扩充
包括bio_b64.c、bio_enc.c、bio_md.c和bio_ok.c,各自实现了BIO_METHOD方法,分别用于base64编解码、对称加解密以及摘要。
2.3 摘要算法EVP封装
由digest.c实现,实现过程中调用了对应摘要算法的回调函数。各个摘要算法提供了自己的EVP_MD静态结构,对应源码为m_xxx.c。
2.4 对称算法EVP封装
由evp_enc.c实现,实现过程调用了具体对称算法函数,实现了Update操作。各种对称算法都提供了一个EVP_CIPHER静态结构,对应源码为e_xxx.c。需要注意的是,e_xxx.c中不提供完整的加解密运算,它只提供基本的对于一个block_size数据的计算,完整的计算由evp_enc.c来实现。当用户想添加一个自己的对称算法时,可以参考e_xxx.c的实现方式。一般用户至少需要实现如下功能:
Ø 构造一个新的静态的EVP_CIPHER结构;
Ø 实现EVP_CIPHER结构中的init函数,该函数用于设置iv,设置加解密标记、以及根据外送密钥生成自己的内部密钥;
Ø 实现do_cipher函数,该函数仅对block_size字节的数据进行对称运算;
Ø 实现cleanup函数,该函数主要用于清除内存中的密钥信息。
2.5 非对称算法EVP封装
主要是以p_开头的文件。其中,p_enc.c封装了公钥加密;p_dec.c封装了私钥解密;p_lib.c实现一些辅助函数;p_sign.c封装了签名函数;p_verify.c封装了验签函数;p_seal.c封装了数字信封;p_open.c封装了解数字信封。
2.6 基于口令的加密
包括p5_crpt2.c、p5_crpt.c和evp_pbe.c。
三、开发实例
3.1 示例1
#include <string.h>
#include <openssl/evp.h>
int main()
{
int ret,which=1;
EVP_CIPHER_CTX ctx;
const EVP_CIPHER *cipher;
unsigned char key[24],iv[8],in[100],out[108],de[100];
int i,len,inl,outl,total=0;
for(i=0;i<24;i++)
{
memset(&key[i],i,1);
}
for(i=0;i<8;i++)
{
memset(&iv[i],i,1);
}
for(i=0;i<100;i++)
{
memset(&in[i],i,1);
}
EVP_CIPHER_CTX_init(&ctx);
printf("please select :\n");
printf("1: EVP_des_ede3_ofb\n");
printf("2: EVP_des_ede3_cbc\n");
scanf("%d",&which);
if(which==1)
cipher=EVP_des_ede3_ofb();
else
cipher=EVP_des_ede3_cbc();
ret=EVP_EncryptInit_ex(&ctx,cipher,NULL,key,iv);
if(ret!=1)
{
printf("EVP_EncryptInit_ex err1!\n");
return -1;
}
inl=50;
len=0;
EVP_EncryptUpdate(&ctx,out+len,&outl,in,inl);
len+=outl;
EVP_EncryptUpdate(&ctx,out+len,&outl,in+50,inl);
len+=outl;
EVP_EncryptFinal_ex(&ctx,out+len,&outl);
len+=outl;
printf("加密结果长度:%d\n",len);
/* 解密 */
EVP_CIPHER_CTX_cleanup(&ctx);
EVP_CIPHER_CTX_init(&ctx);
ret=EVP_DecryptInit_ex(&ctx,cipher,NULL,key,iv);
if(ret!=1)
{
printf("EVP_DecryptInit_ex err1!\n");
return -1;
}
total=0;
EVP_DecryptUpdate(&ctx,de+total,&outl,out,44);
total+=outl;
EVP_DecryptUpdate(&ctx,de+total,&outl,out+44,len-44);
total+=outl;
ret=EVP_DecryptFinal_ex(&ctx,de+total,&outl);
total+=outl;
if(ret!=1)
{
EVP_CIPHER_CTX_cleanup(&ctx);
printf("EVP_DecryptFinal_ex err\n");
return -1;
}
if((total!=100) || (memcmp(de,in,100)))
{
printf("err!\n");
return -1;
}
EVP_CIPHER_CTX_cleanup(&ctx);
printf("test ok!\n");
return 0;
}
输出结果如下:
please select :
1: EVP_des_ede3_ofb
2: EVP_des_ede3_cbc
1
加密结果长度:100
test ok!
please select :
1: EVP_des_ede3_ofb
2: EVP_des_ede3_cbc
2
加密结果长度:104
test ok!
3.2 示例2
#include <string.h>
#include <openssl/evp.h>
int main()
{
int cnid,ret,i,msize,mtype;
int mpktype,cbsize,mnid,mbsize;
const EVP_CIPHER *type;
const EVP_MD *md;
int datal,count,keyl,ivl;
unsigned char salt[20],data[100],*key,*iv;
const char *cname,*mname;
type=EVP_des_ecb();
cnid=EVP_CIPHER_nid(type);
cname=EVP_CIPHER_name(type);
cbsize=EVP_CIPHER_block_size(type);
printf("encrypto nid : %d\n",cnid);
printf("encrypto name: %s\n",cname);
printf("encrypto bock size : %d\n",cbsize);
md=EVP_md5();
mtype=EVP_MD_type(md);
mnid=EVP_MD_nid(md);
mname=EVP_MD_name(md);
mpktype=EVP_MD_pkey_type(md);
msize=EVP_MD_size(md);
mbsize=EVP_MD_block_size(md);
printf("md info : \n");
printf("md type : %d\n",mtype);
printf("md nid : %d\n",mnid);
printf("md name : %s\n",mname);
printf("md pkey type : %d\n",mpktype);
printf("md size : %d\n",msize);
printf("md block size : %d\n",mbsize);
keyl=EVP_CIPHER_key_length(type);
key=(unsigned char *)malloc(keyl);
ivl=EVP_CIPHER_iv_length(type);
iv=(unsigned char *)malloc(ivl);
for(i=0;i<100;i++)
memset(&data[i],i,1);
for(i=0;i<20;i++)
memset(&salt[i],i,1);
datal=100;
count=2;
ret=EVP_BytesToKey(type,md,salt,data,datal,count,key,iv);
printf("generate key value: \n");
for(i=0;i<keyl;i++)
printf("%x ",*(key+i));
printf("\n");
printf("generate iv value: \n");
for(i=0;i<ivl;i++)
printf("%x ",*(iv+i));
printf("\n");
return 0;
}
EVP_BytesToKey函数通过salt以及data数据来生成所需要的key和iv。
输出:
encrypto nid : 29
encrypto name: DES-ECB
encrypto bock size : 8
md info :
md type : 4
md nid : 4
md name : MD5
md pkey type : 8
md size : 16
md block size : 64
generate key value:
54 0 b1 24 18 42 8d dd
generate iv value:
ba 7d c3 97 a0 c9 e0 70
3.3 示例3
#include <openssl/evp.h>
#include <openssl/rsa.h>
int main()
{
int ret,inlen,outlen=0;
unsigned long e=RSA_3;
char data[100],out[500];
EVP_MD_CTX md_ctx,md_ctx2;
EVP_PKEY *pkey;
RSA *rkey;
BIGNUM *bne;
/* 待签名数据*/
strcpy(data,"openssl 编程作者:赵春平");
inlen=strlen(data);
/* 生成RSA密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
rkey=RSA_new();
ret=RSA_generate_key_ex(rkey,1024,bne,NULL);
if(ret!=1) goto err;
pkey=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey,rkey);
/* 初始化*/
EVP_MD_CTX_init(&md_ctx);
ret=EVP_SignInit_ex(&md_ctx,EVP_md5(), NULL);
if(ret!=1)goto err;
ret=EVP_SignUpdate(&md_ctx,data,inlen);
if(ret!=1)goto err;
ret=EVP_SignFinal(&md_ctx,out,&outlen,pkey);
/* 验证签名*/
EVP_MD_CTX_init(&md_ctx2);
ret=EVP_VerifyInit_ex(&md_ctx2,EVP_md5(), NULL);
if(ret!=1) goto err;
ret=EVP_VerifyUpdate(&md_ctx2,data,inlen);
if(ret!=1) goto err;
ret=EVP_VerifyFinal(&md_ctx2,out,outlen,pkey);
if(ret==1)
printf("验证成功\n");
else
printf("验证错误\n");
err:
RSA_free(rkey);
BN_free(bne);
return 0;
}
3.4 示例4
#include <openssl/evp.h>
#include <openssl/rsa.h>
int main()
{
int ret,ekl[2],npubk,inl,outl,total=0,total2=0;
unsigned long e=RSA_3;
char *ek[2],iv[8],in[100],out[500],de[500];
EVP_CIPHER_CTX ctx,ctx2;
EVP_CIPHER *type;
EVP_PKEY *pubkey[2];
RSA *rkey;
BIGNUM *bne;
/* 生成RSA密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
rkey=RSA_new();
ret=RSA_generate_key_ex(rkey,1024,bne,NULL);
pubkey[0]=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pubkey[0],rkey);
type=EVP_des_cbc();
npubk=1;
EVP_CIPHER_CTX_init(&ctx);
ek[0]=malloc(500);
ek[1]=malloc(500);
ret=EVP_SealInit(&ctx,type,ek,ekl,iv,pubkey,1); /* 只有一个公钥*/
if(ret!=1) goto err;
strcpy(in,"openssl 编程");
inl=strlen(in);
ret=EVP_SealUpdate(&ctx,out,&outl,in,inl);
if(ret!=1)goto err;
total+=outl;
ret=EVP_SealFinal(&ctx,out+outl,&outl);
if(ret!=1) goto err;
total+=outl;
memset(de,0,500);
EVP_CIPHER_CTX_init(&ctx2);
ret=EVP_OpenInit(&ctx2,EVP_des_cbc(),ek[0],ekl[0],iv,pubkey[0]);
if(ret!=1) goto err;
ret=EVP_OpenUpdate(&ctx2,de,&outl,out,total);
total2+=outl;
ret=EVP_OpenFinal(&ctx2,de+outl,&outl);
total2+=outl;
de[total2]=0;
printf("%s\n",de);
err:
free(ek[0]);
free(ek[1]);
EVP_PKEY_free(pubkey[0]);
BN_free(bne);
getchar();
return 0;
}
输出结果:openssl 编程