21 EVP¶
21.1 EVP 简介¶
Openssl EVP(high-level cryptographic functions[1])提供了丰富的密码学中的各种函数。Openssl 中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算 法进行了封装。
EVP 主要封装了如下功能函数:
- 1)实现了 base64 编解码 BIO;
- 2)实现了加解密 BIO;
- 3)实现了摘要 BIO;
- 4)实现了 reliable BIO;
- 5)封装了摘要算法;
- 6)封装了对称加解密算法;
- 7)封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
- 7)基于口令的加密(PBE);
- 8)对称密钥处理;
- 9)数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密。 发送给对方时,同时发送对称密钥密文和数据密文。接收方首先用自己的私钥解密密钥 密文,得到对称密钥,然后用它解密数据。
- 10)其他辅助函数。
21.2 数据结构¶
EVP 数据结构定义在 crypto/evp.h 中
EVP_PKEY¶
该结构用来存放非对称密钥信息,可以是 RSA、DSA、DH 或 ECC 密钥。其中,ptr 用来存放密钥结构地址,attributes 堆栈用来存放密钥属性
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:395
*/
/*
* Type needs to be a bit field Sub-type needs to be for variations on the
* method, as in, can it do arbitrary encryption....
*/
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 */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:93
*/
typedef struct evp_pkey_st EVP_PKEY;
EVP_MD¶
该结构用来存放摘要算法信息、非对称算法类型以及各种计算函数。主要各项意义如 下:
- type: 摘要类型,一般是摘要算法 NID;
- pkey_type: 公钥类型,一般是签名算法 NID;
- md_size: 摘要值大小,为字节数;
- flags: 用于设置标记;
- init: 摘要算法初始化函数;
- update: 多次摘要函数;
- final: 摘要完结函数;
- copy: 摘要上下文结构复制函数;
- cleanup: 清除摘要上下文函数;
- sign: 签名函数,其中 key 为非对称密钥结构地址;
- verify: 摘要函数,其中 key 为非对称密钥结构地址。
openssl 对于各种摘要算法实现了上述结构,各个源码位于 cypto/evp 目录下,文件名 以 m_开头。Openssl 通过这些结构来封装了各个摘要相关的运算
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:115
*/
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 */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:91
*/
typedef struct evp_md_st EVP_MD;
EVP_CIPHER¶
该结构用来存放对称加密相关的信息以及算法。主要各项意义如下:
- nid: 对称算法 nid;
- block_size: 对称算法每次加解密的字节数;
- key_len: 对称算法的密钥长度字节数;
- iv_len: 对称算法的填充长度;
- flags: 用于标记;
- init: 加密初始化函数,用来初始化 ctx,key 为对称密钥值,iv 为初始化向量,enc 用于指明是要加密还是解密,这些信息存放在 ctx 中;
- do_cipher: 对称运算函数,用于加密或解密;
- cleanup: 清除上下文函数;
- set_asn1_parameters: 设置上下文参数函数;
- get_asn1_parameters: 获取上下文参数函数;
- ctrl: 控制函数;
- app_data: 用于存放应用数据。
openssl 对于各种对称算法实现了上述结构,各个源码位于 cypto/evp 目录下,文件名 以 e_开头。Openssl 通过这些结构来封装了对称算法相关的运算。
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:131
*/
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 */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:89
*/
typedef struct evp_cipher_st EVP_CIPHER;
EVP_CIPHER_CTX¶
- 对称算法上下文结构
- 此结构主要用来维护加解密状态,存放中间以及最后结果。因 为加密或解密时,当数据很多时,可能会用到 Update 函数,并且每次加密或解密的 输入数据长度任意的,并不一定是对称算法 block_size 的整数倍,所以需要用该结构 来存放中间未加密的数据。主要项意义如下:
- cipher: 指明对称算法;
- engine: 硬件引擎;
- encrypt: 是加密还是解密;非 0 为加密,0 为解密;
- buf 和 buf_len: 指明还有多少数据未进行运算;
- oiv: 原始初始化向量;
- iv: 当前的初始化向量;
- final: 存放最终结果,一般与 Final 函数对应。
/*
openssl-1.1.1/crypto/evp/evp_locl.h:24
*/
struct evp_cipher_ctx_st {
const EVP_CIPHER *cipher;
ENGINE *engine; /* functional reference if 'cipher' is
* ENGINE-provided */
int encrypt; /* encrypt or decrypt */
int buf_len; /* number we have left */
unsigned char oiv[EVP_MAX_IV_LENGTH]; /* original iv */
unsigned char iv[EVP_MAX_IV_LENGTH]; /* working iv */
unsigned char buf[EVP_MAX_BLOCK_LENGTH]; /* saved partial block */
int num; /* used by cfb/ofb/ctr mode */
/* FIXME: Should this even exist? It appears unused */
void *app_data; /* application stuff */
int key_len; /* May change for variable length cipher */
unsigned long flags; /* Various flags */
void *cipher_data; /* per EVP data */
int final_used;
int block_mask;
unsigned char final[EVP_MAX_BLOCK_LENGTH]; /* possible final block */
} /* EVP_CIPHER_CTX */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:90
*/
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
21.3 源码结构¶
evp 源码位于 crypto/evp 目录,可以分为如下几类:
- 全局函数
主要包括 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)。
- BIO 扩充
包括 bio_b64.c、bio_enc.c、bio_md.c 和 bio_ok.c,各自实现了BIO_METHOD法,分别用于 base64 编解码、对称加解密以及摘要。
- 摘要算法EVP封装
由 digest.c 实现,实现过程中调用了对应摘要算法的回调函数。各个摘要算法 提供了自己的 EVP_MD 静态结构,对应源码为 m_xxx.c。
- 对称算法 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函数,该函数主要用于清除内存中的密钥信息。
- 非对称算法EVP封装
主要是以 p_开头的文件。其中,p_enc.c 封装了公钥加密;p_dec.c 封装了私钥解密;p_lib.c 实现一些辅助函数;p_sign.c 封装了签名函数;p_verify.c 封装了验签函数;p_seal.c 封装了数字信封;p_open.c 封装了解数字信封。
- 基于口令的加密
包括 p5_crpt2.c、p5_crpt.c 和 evp_pbe.c。
21.4 摘要函数¶
查看帮助 : main EVP_md5
典型的摘要函数主要有:
1) EVP_md5
返回 md5 的 EVP_MD。
2) EVP_sha1
返回 sha1 的 EVP_MD。
3) EVP_sha256
返回 sha256 的 EVP_MD。
4) EVP_DigestInit
摘要初使化函数,需要有 EVP_MD 作为输入参数。
5) EVP_DigestUpdate 和 EVP_DigestInit_ex
摘要 Update 函数,用于进行多次摘要。
6) EVP_DigestFinal 和 EVP_DigestFinal_ex
摘要 Final 函数,用户得到最终结果。
7) EVP_Digest
对一个数据进行摘要,它依次调用了上述三个函数。
21.5 对称加解密函数¶
典型的加解密函数主要有:
查看帮助 : main EVP_CIPHER_CTX_init
1) EVP_CIPHER_CTX_init
初始化对称计算上下文。
2) EVP_CIPHER_CTX_cleanup
清除对称算法上下文数据,它调用用户提供的销毁函数销清除存中的内部密钥 以及其他数据。
3) EVP_des_ede3_ecb
返回一个 EVP_CIPHER;
4) EVP_EncryptInit 和 EVP_EncryptInit_ex
加密初始化函数,本函数调用具体算法的 init 回调函数,将外送密钥 key 转换为内部密钥形式,将初始化向量 iv 拷贝到 ctx 结构中。
5) EVP_EncryptUpdate
加密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
6) EVP_EncryptFinal 和 EVP_EncryptFinal_ex
获取加密结果,函数可能涉及填充,它调用了具体算法的 do_cipher 回调函数。
7) EVP_DecryptInit 和 EVP_DecryptInit_ex
解密初始化函数。
8) EVP_DecryptUpdate
解密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
9) EVP_DecryptFinal 和 EVP_DecryptFinal_ex
获取解密结果,函数可能涉及去填充,它调用了具体算法的 do_cipher 回调函数。
10) EVP_BytesToKey
计算密钥函数,它根据算法类型、摘要算法、salt 以及输入数据计算出一个对称密钥和初始化向量iv.
11) PKCS5_PBE_keyivgen 和 PKCS5_v2_PBE_keyivgen
实现了 PKCS5 基于口令生成密钥和初始化向量的算法。
12) PKCS5_PBE_add
加载所有 openssl 实现的基于口令生成密钥的算法。
13) EVP_PBE_alg_add
添加一个 PBE 算法。
#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_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX *ctx2 = EVP_CIPHER_CTX_new();
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);
EVP_CIPHER_CTX_init(ctx);
ek[0]=malloc(500);
ek[1]=malloc(500);
//ret=EVP_SealInit(&ctx,type,ek,ekl,iv,pubkey,1);
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);
ret=EVP_SealUpdate(ctx,out,&outl,in,inl);
if(ret!=1) goto err;
total+=outl;
//ret=EVP_SealFinal(&ctx,out+outl,&outl);
ret=EVP_SealFinal(ctx,out+outl,&outl);
if(ret!=1) goto err;
total+=outl;
memset(de,0,500);
//EVP_CIPHER_CTX_init(&ctx2);
EVP_CIPHER_CTX_init(ctx2);
//ret=EVP_OpenInit(&ctx2,EVP_des_cbc(),ek[0],ekl[0],iv,pubkey[0]);
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);
ret=EVP_OpenUpdate(ctx2,de,&outl,out,total);
total2+=outl;
//ret=EVP_OpenFinal(&ctx2,de+outl,&outl);
ret=EVP_OpenFinal(ctx2,de+outl,&outl);
total2+=outl;
de[total2]=0;
printf("%s\n",de);
err:
EVP_CIPHER_CTX_free(ctx);
EVP_CIPHER_CTX_free(ctx2);
free(ek[0]);
free(ek[1]);
EVP_PKEY_free(pubkey[0]);
BN_free(bne);
getchar();
return 0;
}