OpenSSL中文手册之EVP库详解

  版权声明本文根据DragonKing牛,E-Mail:wzhah@263.NET发布在https://openssl.126.com的系列文章整理修改而成(这个网站已经不能访问了),我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!


1EVP 概览

1.1 EVP 简介

  Openssl EVP(high-level cryptographic functions[1])提供了丰富的密码学中的各种函数。Openssl 中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算法进行了封装。
  EVP系列的函数的声明包含在”evp.h”里面,这是一系列封装了openssl>加密库里面所有算法的函数。通过这样的统一的封装,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密。
  EVP系列函数主要封装了加密、摘要、编码三大类型的算法,使用算法前需要调用OpenSSL_add_all_algorithms函数。
  其中以加密算法与摘要算法为基本,公开密钥算法是对数据加密采用了对称加密算法,对密钥采用非对称加密(公钥加密,私钥解密)。数字签名是非对称算法(私钥签名,公钥认证)。
  EVP 主要封装了如下功能函数:

  • 实现了base64 编解码BIO;
  • 实现了加解密BIO;
  • 实现了摘要BIO;
  • 实现了reliable BIO;
  • 封装了摘要算法;
  • 封装了对称加解密算法;
  • 封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
  • 基于口令的加密(PBE);
  • 对称密钥处理;
  • 数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密。发送给对方时,同时发送对称密钥密文和数据密文。接收方首先用自己的私钥解密密钥密文,得到对称密钥,然后用它解密数据。
  • 其他辅助函数。

1.2 源码结构

evp 源码位于crypto/evp 目录,可以分为如下几类:

1.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)。

1.2.2 BIO 扩充

  包括 bio_b64.c、bio_enc.c、bio_md.c 和bio_ok.c,各自实现了BIO_METHOD方法,分别用于base64 编解码、对称加解密以及摘要。

1.2.3 摘要算法封装

  由 digest.c 实现,实现过程中调用了对应摘要算法的回调函数。各个摘要算法提供了自己的EVP_MD 静态结构,对应源码为m_xxx.c。

1.2.4 对称算法封装

  由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 函数,该函数主要用于清除内存中的密钥信息。

1.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 封装了解数字信封。

1.2.6 基于口令的加密

  包括 p5_crpt2.c、p5_crpt.c 和evp_pbe.c。
注意:
  自从出现engin版本以后,所有对称加密算法和摘要算法可以用ENGINE模块实现的算法代替。如果ENGINE模块实现的对称加密和信息摘要函数被注册为缺省的实现算法,那么当使用各种EVP函数时,软件编译的时候会自动将该实现模块连接进去。

1.4主要函数

1.4.1 对称加解密函数

  • EVP_BytesToKey
      计算密钥函数,它根据算法类型、摘要算法、salt 以及输入数据计算出一个对称密钥和初始化向量iv。
  • PKCS5_PBE_keyivgen 和PKCS5_v2_PBE_keyivgen
      实现了 PKCS5 基于口令生成密钥和初始化向量的算法。
  • PKCS5_PBE_add
      加载所有 openssl 实现的基于口令生成密钥的算法。
  • EVP_PBE_alg_add
      添加一个 PBE 算法。

1.4.2 其他函数

  • EVP_add_cipher
      将对称算法加入到全局变量,以供调用。
  • EVP_add_digest
      将摘要算法加入到全局变量中,以供调用。
  • EVP_CIPHER_CTX_ctrl
      对称算法控制函数,它调用了用户实现的ctrl 回调函数。
  • EVP_CIPHER_CTX_set_key_length
      当对称算法密钥长度为可变长时,设置对称算法的密钥长度。
    8 EVP_CIPHER_CTX_set_padding
      设置对称算法的填充,对称算法有时候会涉及填充。加密分组长度大于一时,用户输入数据不是加密分组的整数倍时,会涉及到填充。填充在最后一个分组来完
    成,openssl 分组填充时,如果有n 个填充,则将最后一个分组用n 来填满。
  • EVP_CIPHER_get_asn1_iv
      获取原始iv,存放在ASN1_TYPE 结构中。
  • EVP_CIPHER_param_to_asn1
      设置对称算法参数,参数存放在ASN1_TYPE 类型中,它调用用户实现的回调函数set_asn1_parameters 来实现。
  • EVP_CIPHER_type
      获取对称算法的类型。
  • EVP_CipherInit/EVP_CipherInit_ex
      对称算法计算(加/解密)初始化函数,_ex 函数多了硬件enginge 参数,EVP_EncryptInit 和EVP_DecryptInit 函数也调用本函数。
  • EVP_CipherUpdate
      对称计算(加/解密)函数,它调用了EVP_EncryptUpdate 和EVP_DecryptUpdate函数。
  • EVP_CipherFinal/EVP_CipherFinal_ex
      对称计算( 加/ 解) 函数, 调用了EVP_EncryptFinal ( _ex ) 和EVP_DecryptFinal(_ex);本函数主要用来处理最后加密分组,可能会有对称计算。
  • EVP_cleanup
      清除加载的各种算法,包括对称算法、摘要算法以及PBE 算法,并清除这些
    算法相关的哈希表的内容。
  • EVP_get_cipherbyname
      根据字串名字来获取一种对称算法(EVP_CIPHER),本函数查询对称算法哈希
    表。
  • EVP_get_digestbyname
      根据字串获取摘要算法(EVP_MD),本函数查询摘要算法哈希表。
  • EVP_get_pw_prompt
      获取口令提示信息字符串.
  • int EVP_PBE_CipherInit(ASN1_OBJECT *pbe_obj, const char *pass, int passlen,
    ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de)
      PBE 初始化函数。本函数用口令生成对称算法的密钥和初始化向量,并作加/
    解密初始化操作。本函数再加上后续的EVP_CipherUpdate 以及EVP_CipherFinal_ex构成一个完整的加密过程(可参考crypto/p12_decr.c 的PKCS12_pbe_crypt 函数).
  • EVP_PBE_cleanup
      删除所有的PBE 信息,释放全局堆栈中的信息.
  • EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *p8)
      将PKCS8_PRIV_KEY_INFO(x509.h 中定义)中保存的私钥转换为EVP_PKEY结构。
  • EVP_PKEY2PKCS8/EVP_PKEY2PKCS8_broken
      将EVP_PKEY 结构中的私钥转换为PKCS8_PRIV_KEY_INFO 数据结构存储。
  • EVP_PKEY_bits
      非对称密钥大小,为比特数。
  • EVP_PKEY_cmp_parameters
      比较非对称密钥的密钥参数,用于DSA 和ECC 密钥。
  • EVP_PKEY_copy_parameters
      拷贝非对称密钥的密钥参数,用于DSA 和ECC 密钥。
  • EVP_PKEY_free
      释放非对称密钥数据结构。
  • EVP_PKEY_get1_DH/EVP_PKEY_set1_DH
      获取/设置EVP_PKEY 中的DH 密钥。
  • EVP_PKEY_get1_DSA/EVP_PKEY_set1_DSA
      获取/设置EVP_PKEY 中的DSA 密钥。
  • EVP_PKEY_get1_RSA/EVP_PKEY_set1_RSA
      获取/设置EVP_PKEY 中结构中的RSA 结构密钥。
  • EVP_PKEY_missing_parameters
      检查非对称密钥参数是否齐全,用于DSA 和ECC 密钥。
  • EVP_PKEY_new
      生成一个EVP_PKEY 结构。
  • EVP_PKEY_size
      获取非对称密钥的字节大小。
  • EVP_PKEY_type
      获取EVP_PKEY 中表示的非对称密钥的类型。
  • int EVP_read_pw_string(char *buf,int length,const char *prompt,int verify)
      获取用户输入的口令;buf 用来存放用户输入的口令,length 为buf 长度,prompt为提示给用户的信息,如果为空,它采用内置的提示信息,verify 为0 时,不要求验证用户输入的口令,否则回要求用户输入两遍。返回0 表示成功。
  • EVP_set_pw_prompt
      设置内置的提示信息,用于需要用户输入口令的场合。

2 对称加密

  EVP加密算法包括了对称加密算法和非对称加密算法.

  • 函数名称:EVP_Encrypt*…,EVP_Cipher…*
  • 功能描述:该系列函数封装提供了对称加密算法的功能。
  • 相关文件:evp_enc.c、e_*.c

2.1 基本数据结构

  EVP_CIPHER与EVP_CIPHER_CTX两个基本结构,加密函数EVP_Encrypt(EVP_Cipher)一些列函数都是以这两个结构为基础实现了。文件evp_enc.c是最高层的封装实现,,而各个e_*.c文件则是真正实现了各种算法的加解密功能,当然它们其实也是一些封装函数,真正的算法实现在各个算法同名目录里面的文件实现。
  注意: EVP_CIPHER是、EVP_CIPHER_CTX的成员,在加密时通过指定的加密算法(其实就是加密函数),返回对应的EVP_CIPHER的指针,然后EVP_EncryptInit函数中 调用 EVP_CIPHER来初化EVP_CIPHER_CTX。

2.1.1 EVP_CIPHER结构体

#include<openssl/evp.h>

typedef struct evp_cipher_st
{
    int nid;               //是算法类型的nid识别号,openssl里面每个对象都有一个内部唯一的识别ID
    int block_size;        //是每次加密的数据块的长度,以字节为单位
    int key_len;           //是每次加密的数据块的长度,以字节为单位
    int iv_len;            //初始化向量的长度
    unsigned long flags;   //标志位
    int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc);
                           //算法结构初始化函数,可以设置为加密模式还是解密模式
    int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl);                  //进行数据加密或解密的函数
    int (*cleanup)(EVP_CIPHER_CTX *);   //释放EVP_CIPHER_CTX结构里面的数据和设置
    int ctx_size;                       //设定ctx->cipher_data数据的长度
    int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);                          
                                        // 在EVP_CIPHER_CTX结构中通过参数设置一个ASN1_TYPE
    int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);        //从一个ASN1_TYPE中取得参数
    int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr);      //其它各种操作函数
    void *app_data;                                                   //应用数据
}EVP_CIPHER;

2.1.2 EVP_CIPHER_CTX结构体

#include<openssl/evp.h>
typedef struct evp_cipher_ctx_st
{
    const EVP_CIPHER *cipher;  //是该结构相关的一个EVP_CIPHER算法结构
    ENGINE *engine;            //如果加密算法是ENGINE提供的,那么该成员保存了相关的函数接口
    int encrypt;               //加密或解密的标志
    int buf_len;               //该结构缓冲区里面当前的数据长度
    unsigned char oiv[EVP_MAX_IV_LENGTH];      //初始的初始化向量
    unsigned char iv[EVP_MAX_IV_LENGTH];       //工作时候使用的初始化向量
    unsigned char buf[EVP_MAX_BLOCK_LENGTH];   //保存下来的部分需要数据
    int num;                   //在cfb/ofb模式的时候指定块长度
    void *app_data;            //应用程序要处理数据
    int key_len;               //密钥长度,算法不一样长度也不一样
    unsigned long flags; 
    void *cipher_data;         //加密后的数据
    int final_used;
    int block_mask;
    unsigned char final[EVP_MAX_BLOCK_LENGTH];//
} EVP_CIPHER_CTX;

2.2 相关函数

  所在文件evp_enc.c、evp.h。

2.2.1 核心函数

  EVP_*crypt系列函数只是对EVP_Cipher函数的调用,EVP_Encrypt函数相当于对EVP_Cipher函数enc参数置为1,EVP_Decrypt函数相当于对EVP_Cipher函数enc参数置为0。

2.2.1.1 底层函数
  • 旧版本
#include<openssl/evp.h>

int EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, 
                    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(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
  • 新版本
int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, 
                      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 *out, int *outl)

【EVP_CipherInit_ex, EVP_CipherUpdate和EVP_CipherFinal_ex】
  事实上,后面介绍的函数都是调用这三个函数实现的,它们是更底层的函数。完成了数据的加密和解密功能。他们根据参数enc决定执行加密还是解密操作,如果enc为1,则加密;如果enc为0,则解密;如果enc是-1,则不改变数据。三个函数都是操作成功返回1,否则返回0。
  注意:两个版本中:EVP_EncryptInit,EVP_DecryptInit和EVP_CipherInit,这三个函数的功能分别跟函数EVP_EncryptInit_ex,EVP_DecryptInit_ex和EVP_CipherInit_ex功能相同,只是他们的ctx参数不需要进行初始化,并且使用缺省的算法库。三个函数都是操作成功返回1,否则返回0。 EVP_EncryptFinal, EVP_DecryptFinal和EVP_CipherFinal,这三个函数分别跟函数EVP_EncryptFinal_ex,EVP_DecryptFinal_ex以及EVP_CipherFinal_ex函数功能相同,不过,他们的参数ctx会在调用后自动释放。三个函数都是操作成功返回1,否则返回0。

2.2.1.2 加密
  • 旧版本
int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, 
                    const unsigned char *key, const unsigned char *iv)
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, 
                      int *outl,const unsigned char *in, int inl)
int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
  • 新版本
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
                       ENGINE *impl, const unsigned char *key,const unsigned char *iv)
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, 
                       int *outl,const unsigned char *in, int inl)
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)

【EVP_EncryptInit_ex】
  该函数采用ENGINE参数impl的算法来设置并初始化加密结构体。其中,参数ctx必须在调用本函数之前已经进行了初始化。参数type通常通过函数类型来提供参数,如EVP_des_cbc函数的形式,即我们上一章中介绍的对称加密算法的类型。如果参数impl为NULL,那么就会使用缺省的实现算法。参数key是用来加密的对称密钥,iv参数是初始化向量(如果需要的话)。在算法中真正使用的密钥长度和初始化密钥长度是根据算法来决定的。(也就是你传入的key或者iv长度可以是任意的,实际使用的数据取决于算法,不足会自动补上,超过会自动舍去)在调用该函数进行初始化的时候,除了参数cipher之外,所有其它参数可以设置为NULL,留到以后调用其它函数的时候再提供,这时候参数cipher就设置为NULL就可以了。在缺省的加密参数不合适的时候,可以这样处理。操作成功返回1,否则返回0。

【EVP_EncryptUpdate】
  该函数执行对数据的加密。该函数加密从参数in输入的长度为inl的数据,并将加密好的数据写入到参数out里面去。可以通过反复调用该函数来处理一个连续的数据块。写入到out的数据数量是由已经加密的数据的对齐关系决定的,理论上来说,从0到(inl+cipher_block_size-1)的任何一个数字都有可能(单位是字节),所以输出的参数out要有足够的空间存储数据。写入到out中的实际数据长度保存在outl参数中。操作成功返回1,否则返回0。

【EVP_EncryptFinal_ex】
  该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。

   PKCS 填充(padding)标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么7个值为7的字节就应该增加在数据的后面。

2.2.1.3 解密
  • 旧版本
int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
                    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(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
  • 新版本
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
                       ENGINE *impl, const unsigned char *key, const unsigned char *iv)  
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                      const unsigned 
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝月心语

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值