OpenSSL中文手冊之EVP庫詳解

  聲明:OpenSS系列文章是根據DragonKing, Mail: [email protected]發佈在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.2 對稱加解密函數
* EVP_BytesToKey
  計算密鑰函數,它根據算法類型、摘要算法、salt 以及輸入數據計算出一個對稱密鑰和初始化向量iv。
* PKCS5_PBE_keyivgen 和PKCS5_v2_PBE_keyivgen
  實現了 PKCS5 基於口令生成密鑰和初始化向量的算法。
* PKCS5_PBE_add
  加載所有 openssl 實現的基於口令生成密鑰的算法。
* EVP_PBE_alg_add
  添加一個 PBE 算法。


###1.4.5 其他函數
* 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
  設置內置的提示信息,用於需要用戶輸入口令的場合。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 新版本
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)
  • 1
  • 2
  • 3
  • 4
  • 5

【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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 新版本
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)
  • 1
  • 2
  • 3
  • 4
  • 5

【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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 新版本
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 char *in, int inl)                    
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
  • 1
  • 2
  • 3
  • 4
  • 5

【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】
  這三個函數是上面三個函數相應的解密函數。這些函數的參數要求基本上都跟上面相應的加密函數相同。如果填充(padding)功能打開了,EVP_DecryptFinal會檢測最後一段數據的格式,如果格式不正確,該函數會返回錯誤代碼。此外,如果打開了padding功能,EVP_DecryptUpdate函數的參數out的長度應該至少爲(inl+cipher_block_size)字節;但是,如果塊的長度爲1,則其長度爲inl字節就足夠了。三個函數都是操作成功返回1,否則返回0。
   需要注意的是,雖然在padding功能開啓的情況下,解密操作提供了錯誤檢測功能,但是該功能並不能檢測輸入的數據或密鑰是否正確,所以即便一個隨機的數據塊也可能無錯的完成該函數的調用。如果padding功能關閉了,那麼當解密數據長度是塊長度的整數倍時,操作總是返回成功的結果。

2.2.2 輔助函數

2.2.2.1 操作EVP_CIPHER_CTX的函數

int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c)    //重置EVP_CIPHER_CTX

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void)       //開闢EVP_CIPHER_CTX
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx)  //銷燬之前開闢的EVP_CIPHER_CTX

void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);    
int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

【EVP_CIPHER_CTX_init】
  該函數初始化一個EVP_CIPHER_CTX結構體,只有初始化後該結構體才能在下面介紹的函數中使用。無返回值。

【EVP_CIPHER_CTX_cleanup】
  該函數清除一個EVP_CIPHER_CTX結構中的所有信息並釋放該結構佔用的所有內存。在使用上述的函數完成一個加密算法過程後應該調用該函數,這樣可以避免一些敏感信息遺留在內存造成安全隱犯。成功返回1,否則返回0

2.2.2.2 參數設置與獲取函數

#define EVP_MAX_IV_LENGTH 16
#define EVP_MAX_BOLCK_LENGTH 32
#define EVP_MAX_KEY_LENGTH 64

int     is_partially_overlapping(const void *ptr1, const void *ptr2, int len)
int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *x, int padding);
int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen);
int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr);


const EVP_CIPHER *EVP_get_cipherbyname(const char *name);
#define EVP_get_cipherbynid(a) EVP_get_cipherbyname(OBJ_nid2sn(a))
#define EVP_get_cipherbyobj(a) EVP_get_cipherbynid(OBJ_obj2nid(a))

#define EVP_CIPHER_nid(e)              ((e)->nid)
#define EVP_CIPHER_block_size(e)       ((e)->block_size)
#define EVP_CIPHER_key_length(e)       ((e)->key_len)
#define EVP_CIPHER_iv_length(e)                ((e)->iv_len)
#define EVP_CIPHER_flags(e)            ((e)->flags)
#define EVP_CIPHER_mode(e)             ((e)->flags) & EVP_CIPH_MODE)
int EVP_CIPHER_type(const EVP_CIPHER *ctx);

#define EVP_CIPHER_CTX_cipher(e)       ((e)->cipher)
#define EVP_CIPHER_CTX_nid(e)          ((e)->cipher->nid)
#define EVP_CIPHER_CTX_block_size(e)   ((e)->cipher->block_size)
#define EVP_CIPHER_CTX_key_length(e)   ((e)->key_len)
#define EVP_CIPHER_CTX_iv_length(e)    ((e)->cipher->iv_len)
#define EVP_CIPHER_CTX_get_app_data(e) ((e)->app_data)
#define EVP_CIPHER_CTX_set_app_data(e,d) ((e)->app_data=(char *)(d))
#define EVP_CIPHER_CTX_type(c)         EVP_CIPHER_type(EVP_CIPHER_CTX_cipher(c))
#define EVP_CIPHER_CTX_flags(e)                ((e)->cipher->flags)
#define EVP_CIPHER_CTX_mode(e)         ((e)->cipher->flags & EVP_CIPH_MODE)

int EVP_CIPHER_param_to_asn1(EVP_CIPHER_CTX *c, ASN1_TYPE *type);
int EVP_CIPHER_asn1_to_param(EVP_CIPHER_CTX *c, ASN1_TYPE *type);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

【EVP_CIPHER_CTX_set_padding】
  該函數設置是否採用填充(padding)功能.在算法缺省的情況下,是使用標準的塊填充功能的,並且在解密的時候會自動檢測填充數據並將其刪除。如果將參數pad設置爲0,則padding功能就會被禁止,那麼在加密和解密的時候,此時數據應該爲加密塊長度的整數倍,否則就會出錯。函數恆返回1

【EVP_CIPHER_CTX_set_key_length】
  該函數進行加密算法結構EVP_CIPHER_CTX密鑰長度的設置。如果算法是一個密鑰長度固定的算法,那麼如果設置的密鑰長度跟它固定的長度不一致,就會產生錯誤。

【EVP_get_cipherbyname, EVP_get_cipherbynid和EVP_get_cipherbyobj】
  這三個函數都根據給定的參數返回一個EVP_CIPHER結構,不同的是給定的參數分別是算法名稱、算法的NID和一個ASN1_OBJECT結構。具體的算法名稱、NID以及ASN1_OBJECT結構請參看object/boject.h文件的定義。成功返回對應的EVP_CIPHER* ,失敗返回NULL。

【EVP_CIPHER_nid和EVP_CIPHER_CTX_nid】
  這兩個函數返回EVP_CIPHER或EVP_CIPHER_CTX結構內部的算法的NID。返回的NID值只是一個內部存儲的值,並不一定真的有相應的OBJECT定義。返回EVP_CIPHER的nid成員的值。

【EVP_CIPHER_key_length和EVP_CIPHER_CTX_key_length】
  這兩個函數返回EVP_CIPHER或EVP_CIPHER_CTX結構內部的算法的密鑰長度。常量EVP_MAX_KEY_LENGTH定義了所有算法最長的密鑰長度。需要注意的是,對於EVP_CIPHER_key_length函數來說,對特定的一種算法密鑰長度是不變的,但是EVP_CIPHER_CTX_key_length函數對同一個算法密鑰長度卻是可變的。

【EVP_CIPHER_iv_length和EVP_CIPHER_CTX_iv_length】
  這兩個函數返回EVP_CIPHER或EVP_CIPHER_CTX結構內部的算法的初始化向量長度。如果算法不使用IV,那麼就會返回0。常量EVP_MAX_IV_LENGTH定義了所有算法最長的IV長度。

【EVP_CIPHER_block_size和EVP_CIPHER_CTX_block_size】
  這兩個函數返回EVP_CIPHER或EVP_CIPHER_CTX結構內部的算法的加密塊長度。常量EVP_MAX_IV_LENGTH也是所有算法最長的塊長度。

【EVP_CIPHER_type和EVP_CIPHER_CTX_type】
  這兩個函數返回EVP_CIPHER或EVP_CIPHER_CTX結構內部的算法的類型。該類型的值是算法的NID,一般來說,NID忽略了算法的一些參數,如40位和129位RC2算法的NID是相同的。如果算法沒有相應定義的NID或者不是ASN1所支持的,那麼本函數就會返回NID_undef。

【EVP_CIPHER_CTX_cipher】
  該函數返回EVP_CIPHER_CTX結構裏面的EVP_CIPHER結構

【EVP_CIPHER_mode和EVP_CIPHER_CTX_mode】
  這兩個函數返回相應結構算法的塊加密模式,包括EVP_CIPH_ECB_MODE, EVP_CIPH_CBC_MODE, EVP_CIPH_CFB_MODE和EVP_CIPH_OFB_MODE;如果算法是流加密算法,那麼就返回EVP_CIPH_STREAM_CIPHER 。

【EVP_CIPHER_param_to_asn1】
  該函數設置算法結構的參數,一般來說設置的值包括了所有參數和一個IV值。如果算法有IV,那麼調用該函數時IV是必須設置的。該函數必須在所設置的算法結構使用之前(如調用EVP_EncryptUpdate和EVP_DecryptUpdate函數之前)調用。如果ASN1不支持該算法,那麼調用該函數將導致失敗。操作成功返回1,否則返回0

【EVP_CIPHER_asn1_to_param】
  該函數給用算法結構裏面的值設置參數type的結構。其設置的內容由具體的算法決定。如在RC2算法中,它會設置IV和有效密鑰長度。本函數應該在算法結構的基本算法類型已經設置了但是密鑰還沒有設置之前調用。例如,調用EVP_CipherInit函數的時候使用參數IV,並將key設置位NULL,然後就應該調用本函數,最後再調用EVP_CipherInit,這時候除了key設置位NULL外所有參數都應該設置。當ASN1不支持不支持該算法或者有參數不能設置的時候(如RC2的有效密鑰長度不支持),該函數調用就會失敗。操作成功返回1,否則返回0

【EVP_CIPHER_CTX_ctrl】
  該函數可以設置不同算法的特定的參數。目前只有RC2算法的有效密鑰長度和RC5算法的加密次數(rounds)可以進行設置。

【KCS5_PBE_keyivgen 和PKCS5_v2_PBE_keyivgen】
  實現了 PKCS5 基於口令生成密鑰和初始化向量的算法。

【PKCS5_PBE_add】
  加載所有 openssl 實現的基於口令生成密鑰的算法。

【EVP_PBE_alg_add】
  添加一個 PBE 算法。

2.2.3 算法函數

  openssl對稱加密算法的格式都以函數形式提供,其實該函數返回一個該算法的結構體,其形式一般如下(evp.h 、e_*.c):
     EVP_CIPHER* EVP_加密算法(void)
  在openssl中,所有提供的對稱加密算法長度都是固定的,有特別說明的除外。下面對這些算法進行分類的介紹,首先介紹一下算法中使用的通用標誌的含義。

2.2.3.1 分組加密的迭代模式

  • ecb——電子密碼本(Electronic Code Book)加密方式
  • cbc——加密塊鏈接(Cipher Block Chaining)加密方式
  • cfb——64位加密反饋(Cipher Feedback)加密方式
  • ofb——64位輸出反饋(Output Feedback)加密方式
  • ede——該加密算法採用了加密、解密、加密的方式,第一個密鑰和最後一個密鑰是相同的
  • ede3——該加密算法採用了加密、解密、加密的方式,但是三個密鑰都不相同

2.2.3.2 加密算法

【NULL算法】
   函數:EVP_enc_null()該算法不作任何事情,也就是沒有進行加密處理

【DES算法】
  函數:EVP_des_cbc(void), EVP_des_ecb(void), EVP_des_cfb(void), EVP_des_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的DES算法

【使用兩個密鑰的3DES算法】
   函數:EVP_des_ede_cbc(void), EVP_des_ede(), EVP_des_ede_ofb(void),EVP_des_ede_cfb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的3DES算法,算法的第一個密鑰和最後一個密鑰相同,事實上就只需要兩個密鑰

【使用三個密鑰的3DES算法】
  函數:EVP_des_ede3_cbc(void), EVP_des_ede3(), EVP_des_ede3_ofb(void), EVP_des_ede3_cfb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的3DES算法,算法的三個密鑰都不相同

【DESX算法】
  函數:EVP_desx_cbc(void)
  說明:CBC方式DESX算法

【RC2算法】
  函數:EVP_rc2_cbc(void), EVP_rc2_ecb(void), EVP_rc2_cfb(void), EVP_rc2_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的RC2算法,該算法的密鑰長度是可變的,可以通過設置有效密鑰長度或有效密鑰位來設置參數來改變。缺省的是128位。

【定長的兩種RC2算法】
  函數:EVP_rc2_40_cbc(void), EVP_rc2_64_cbc(void)
  說明:分別是40位和64位CBC模式的RC2算法。

【RC4算法】
  函數:EVP_rc4(void)
  說明:RC4流加密算法。該算法的密鑰長度可以改變,缺省是128位。

【40位RC4算法】
   函數:EVP_rc4_40(void)
  說明:密鑰長度40位的RC4流加密算法。該函數可以使用EVP_rc4和EVP_CIPHER_CTX_set_key_length函數代替

【RC5算法】
  函數:EVP_rc5_32_12_16_cbc(void), EVP_rc5_32_12_16_ecb(void), EVP_rc5_32_12_16_cfb(void), EVP_rc5_32_12_16_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的RC5算法,該算法的密鑰長度可以根據參數“number of rounds”(算法中一個數據塊被加密的次數)來設置,缺省的是128位密鑰,加密次數爲12次。目前來說,由於RC5算法本身實現代碼的限制,加密次數只能設置爲8、12或16。

【IDEA算法】
  函數:EVP_idea_cbc(),EVP_idea_ecb(void), EVP_idea_cfb(void), EVP_idea_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的IDEA算法。

【Blowfish算法】
  函數:EVP_bf_cbc(void), EVP_bf_ecb(void), EVP_bf_cfb(void), EVP_bf_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的Blowfish算法,該算法的密鑰長度是可變的

【CAST算法】
  函數:EVP_cast5_cbc(void), EVP_cast5_ecb(void), EVP_cast5_cfb(void), EVP_cast5_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的CAST算法,該算法的密鑰長度是可變的

【128位AES算法】
  函數:EVP_aes_128_ecb(void),EVP_aes_128_cbc(void),PEVP_aes_128_cfb(void),EVP_aes_128_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的128位AES算法

【192位AES算法】
  函數:EVP_aes_192_ecb(void),EVP_aes_192_cbc(void),PEVP_aes_192_cfb(void),EVP_aes_192_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的192位AES算法

【256位AES算法】
  函數:EVP_aes_256_ecb(void),EVP_aes_256_cbc(void),PEVP_aes_256_cfb(void),EVP_aes_256_ofb(void)
  說明:分別是CBC方式、ECB方式、CFB方式以及OFB方式的256位AES算法

注: 這些加密算法函數調用時返回的都是對應EVP_CIPHER結構體指針。

2.3 應用架構

  一般來說,EVP_Encrypt*…*系列函數的應用架構如下所描述(假設加密算法爲3DES):

  • 定義一些必須的變量
char key[EVP_MAX_KEY_LENGTH];
char iv[EVP_MAX_IV_LENGTH];
EVP_CIPHER_CTX ctx;
unsigned char out[512+8];
int outl;
  • 1
  • 2
  • 3
  • 4
  • 5

  !注意:一般情況下,對於對稱加密算法,尤其是分組加密,輸出數據緩衝區大小要大於輸入數據緩衝區,所以一般輸出緩衝區的大小應設置爲sizeof(array_in)+ EVP_MAX_BLOCK_SIZE,或者sizeof(array_in)+ EVP_CIPHER.block_size,這是因爲分組加密,會按照一定的模式填充塊。

  • 給變量key和iv賦值
      這裏使用了函數EVP_BytesToKey,該函數從輸入密碼產生了密鑰key和初始化向量iv,該函數將在後面做介紹。如果可以有別的辦法設定key和iv,該函數的調用不是必須的
EVP_BytesToKey(EVP_des_ede3_cbc,EVP_md5,NULL,passwd,strlen(passwd),key,iv);
  • 1
  • 初始加密算法結構EVP_CIPHER_CTX
EVP_EncryptInit_ex(&ctx, EVP_des_ede3_cbc(), NULL, key, iv);
  • 1
  • 進行數據的加密操作
    while (....)
    {
     EVP_EncryptUpdate(ctx,out,&outl,in,512);
    }
  • 1
  • 2
  • 3
  • 4

  一般來說採用了循環的結構進行處理,每次循環加密數據爲512字節,密文輸出到out,out和int應該是指向不相同的內存的。

  !注意:EVP庫的EVP_*Update系列函數調用一次就能處理完指針in中的inlen個字節數據。這裏所謂的循環是用於此類情景:每次收到若干字節放入指針in指向的緩衝區中,然後對其處理;或者每次從文件中讀取若干字節到指針in所指緩衝區,再對其處理。如果輸入的數據不是整數倍,則會留到EVP_*_CTX 中,等待下一次Update或EVP_Final*來處理,也就是循環是用於無法一次傳入所有數據的情況。*

  • 結束加密,輸出最後的一段512字節的數據
    EVP_EncryptFinal_ex(&ctx, out, &outl)
  • 1

  該函數會進行加密的檢測,如果加密過程有誤,一般會檢查出來。
  說明:解密跟上述過程是一樣的,只不過要使用EVP_Decrypt*…*系列函數。

2.4 用法示例

//OpenSSL中所有的對稱和摘要算法都需要進行全局初始化,方法如下:
    OpenSSL_add_all_algorithms();
//當然也可以只載入加密算法或摘要算法

//  OpenSSL_add_all_digest();
//  OpenSSL_add_all_cipher();

//如果不經過初始化就調用了加密或摘要相關的EVP接口,則會返回錯誤。

//對稱算法

static int OpenSSL_Cipher(const char *ciphername, int dir, 
              const unsigned char *aKey, const unsigned char *iVec,
              const unsigned char *in, int inlen,
              unsigned char *out, int *poutlen)
{
    int rv = 0, n = 0, tmplen = 0;
    char szErr[1024];

    const EVP_CIPHER *cipher = NULL;
    EVP_CIPHER_CTX ctx;

    /* 初始化加密調用的上下文 */   
    EVP_CIPHER_CTX_init(&ctx);

    /* 根據名稱(如des-cbc,或rc4)獲取CIPHER對象,OpenSSL支持的算法名稱可以用openssl enc -h命令列出 */ 
    cipher = EVP_get_cipherbyname(ciphername);
    if (NULL == cipher) {
        fprintf( stderr, "OpenSSL_Cipher: Cipher for %s is NULL\n", ciphername );

        rv = -1;
        goto err;
    }

    /**
     * 初始化算法:設置對稱算法的密鑰,IV,以及加解密標誌位dir
     * 如果使用Engine,此時會調用其實現的EVP_CIPHER->init回調函數
     */
    if (!EVP_CipherInit_ex(&ctx, cipher, NULL, aKey, iVec, dir)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_CipherInit failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -2;
        goto err;
    }

    /**
     * 對數據進行加/解密運算(如果使用Engine,此時會調用其實現的EVP_CIPHER->do_cipher回調函數)
     * 對於連續數據流,CipherUpdate一般會被調用多次
     */
    if (!EVP_CipherUpdate(&ctx, out, poutlen, in, inlen)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_CipherInit failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -3;
        goto err;
    }

    /**
     * 輸出最後一塊數據結果(塊加密時,數據將被padding到block長度的整數倍,因此會產生額外的最後一段數據)
     * 注意:如果使用Engine,此時會觸發其實現的EVP_CIPHER->do_cipher,而不是EVP_CIPHER->cleanup
     *       這點上與EVP_DigestFinal/EVP_SignFinal/EVP_VerifyFinal是完全不同的
     */ 
    if (!EVP_CipherFinal(&ctx, out + *poutlen, &tmplen)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_CipherInit failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -4;
        goto err;
    }

    *poutlen += tmplen;

err:    
    /* 釋放上下文(如果使用Engine,此時會調用其實現的EVP_CIPHER->cleanup回調函數) */    
    EVP_CIPHER_CTX_cleanup(&ctx);

    return rv;
}

//與OpenSSl_add_all_algorithms正好相反
EVP_cleanup();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

3 摘要

  該系列函數封裝了openssl加密庫所有的信息摘要算法,通過這種EVP封裝,當使用不同的信息摘要算法時,只需要對初始化參數修改一下就可以了,其它代碼可以完全一樣。這些算法包括MD2、MD5以及SHA等算法。

  • 函數名稱:EVP_Digest*…*
  • 功能描述:該系列函數封裝實現了多種信息摘要算法。
  • 相關文件:digest.c,m_*.c

3.1 基本數據結構

  EVP_MD與EVP_MD_CTX兩個基本結構,摘要函數EVP_Digest*一些列函數都是以這兩個結構爲基礎實現了。文件digest.c是最高層的封裝實現,而各個m_*.c文件則是真正實現了各種算法的摘要算法,當然它們其實也是一些封裝函數,真正的算法實現在各個算法同名目錄裏面的文件實現。

3.1.1 EVP_MD結構體

  所有的摘要算法都維護着指向下面定義的結構體的一個指針,在此基礎上實現了算法的功能。該結構EVP_MD如下:

    #include<opessl/evp.h>
    typedef struct env_md_st
    { 
     int type;     //信息摘要算法的NID標識
     int pkey_type;//是信息摘要-簽名算法體制的相應NID標識,如NID_shaWithRSAEncryption
     int md_size;  //是信息摘要算法生成的信息摘要的長度,如SHA算法是SHA_DIGEST_LENGTH,該值是20
     unsigned long flags;
     int (*init)(EVP_MD_CTX *ctx);
               //指向一個特定信息摘要算法的初始化函數,如對於SHA算法,指針指向SHA_Init
     int (*update)(EVP_MD_CTX *ctx,const void *data,unsigned long count); 
               //指向一個真正計算摘要值的函數,例如SHA算法就是指向SHA_Update
     int (*final)(EVP_MD_CTX *ctx,unsigned char *md);
               //指向一個信息摘要值計算之後要調用的函數,該函數完成最後的一塊數據的處理工作。例如SHA算法就是指向SHA_Final.
     int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from);
                      //指向一個可以在兩個EVP_MD_CTX結構之間拷貝參數值的函數
     int (*cleanup)(EVP_MD_CTX *ctx);
     int (*sign)();   //簽名
     int (*verify)(); //認證
     int required_pkey_type[5];
                      //指向一個用來簽名的算法EVP_PKEY的類型,如SHA算法就指向EVP_PKEY_RSA_method
     int block_size;  //一個用來進行信息摘要的輸入塊的的長度(單位是字節),如SHA算法就是SHA_CBLOCK
     int ctx_size;    //是CTX結構的長度,在SHA算法裏面應該就是sizeof(EVP_MD*)+sizeof(SHA_CTX)
    } EVP_MD;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

  如果你要增加新的算法,那麼可以定義這個結構,並進行必要的一直,然後就可以使用通用的函數了。跟EVP_CIPHER系列函數一樣,使用這個封裝技術,就可以在使用一種摘要算法時,比如MD5,在連接程序的時候就只連接MD5的代碼。如果使用證書來標識算法,那麼就會導致所有其它的信息摘要算法代碼都連接到程序中去了。

3.1.2 EVP_MD_CTX結構體

  在調用函數的時候,一般來說需要傳入上面說的type的參數和下面所定義的一個CTX結構,用EVP_MD來初始化EVP_MD_CTX的digest成員,該結構EVP_MD_CTX定義如下:

    typedef struct env_md_ctx_st
    {
     const EVP_MD *digest;  //digest——指向上面介紹的EVP_MD結構的指針
     ENGINE *engine;        //如果算法由ENGINE提供,該指針指向該ENGINE
     unsigned long flags;   //
     void *md_data;         //信息摘要數據
    }EVP_MD_CTX ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.2 相關函數

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

3.2.1 核心函數

3.2.1.1 舊版本

int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type)
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t count)
int EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *size)
  • 1
  • 2
  • 3

【EVP_DigestInit】
  該函數功能跟EVP_DigestInit_ex函數相同,但是ctx參數可以不用初始化,而且該函數只使用缺省實現的算法。成功返回1,失敗返回0。
【EVP_DigestFinal】
  該函數功能跟EVP_DigestFinal_ex函數相同,但是ctx結構會自動清除。一般來說,現在新的程序應該使用EVP_DigestInit_ex和EVP_DigestFinal_ex函數,因爲這些函數可以在使用完一個EVP_MD_CTX結構後,不用重新聲明和初始化該結構就能使用它進行新的數據處理,而且新的帶_ex的函數也可以使用非缺省的實現算法庫。成功返回1,失敗返回0。

3.2.1.2 新版本

int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl)
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t count)
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *size)
  • 1
  • 2
  • 3

【EVP_DigestInit_ex】
  該函數使用參數impl所指向的ENGINE設置該信息摘要結構體,參數ctx在調用本函數之前必須經過初始化。參數type一般是使用象EVP_sha1這樣的函數的返回值。如果impl爲NULL,那麼就會使用缺省實現的信息摘要函數。大多數應用程序裏面impl是設置爲NULL的。操作成功返回1,否則返回0。
【EVP_DigestUpdate】
  該函數將參數d中的cnt字節數據進行信息摘要到ctx結構中去,該函數可以被調用多次,用以對更多的數據進行信息摘要。操作成功返回1,否則返回0。
【EVP_DigestFinal_ex】
  本函數將ctx結構中的摘要信息數據返回到參數md中,如果參數s不是NULL,那麼摘要數據的長度(字節)就會被寫入到參數s中,大多數情況瞎,寫入的值是EVP_MAX_MD_SIZE。在調用本函數後,不能使用相同的ctx結構調用EVP_DigestUpdate再進行數據的信息摘要操作,但是如果調用EVP_DigestInit_ex函數重新初始化後可以進行新的信息摘要操作。操作成功返回1,否則返回0

3.2.1.3 高級版本

int EVP_Digest(const void *data, size_t count,
               unsigned char *md, unsigned int *size, const EVP_MD *type,ENGINE *impl)
  • 1
  • 2

3.2.2 輔助函數

3.2.2.1 操作EVP_MD_CTX的函數

int EVP_MD_CTX_reset(EVP_MD_CTX *ctx)

EVP_MD_CTX *EVP_MD_CTX_new(void)
void EVP_MD_CTX_free(EVP_MD_CTX *ctx)

EVP_MD_CTX *EVP_MD_CTX_create(void);
void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx);

void EVP_MD_CTX_init(EVP_MD_CTX *ctx);
int  EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx);

int  EVP_MD_CTX_copy(EVP_MD_CTX *out, const EVP_MD_CTX *in)
int  EVP_MD_CTX_copy_ex(EVP_MD_CTX *out, const EVP_MD_CTX *in)

int EVP_MD_CTX_ctrl(EVP_MD_CTX *ctx, int cmd, int p1, void *p2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

【EVP_MD_CTX_init】
  該函數初始化一個EVP_MD_CTX結構。
【EVP_MD_CTX_create】
  該函數創建一個EVP_MD_CTX結構,分配內存並進行初始化,返回該結構。
【EVP_MD_CTX_cleanup】
  清除一個信息摘要結構,該函數應該在一個信息摘要結構使用後不再需要的時候調用。
【EVP_MD_CTX_destroy】
  清除信息摘要結構並釋放所有分配的內存空間,只有使用EVP_MD_CTX_create函數創建的信息摘要結構才能使用該函數進行釋放。
【EVP_MD_CTX_copy_ex】
  該函數可以用來將信息摘要數據從in結構拷貝到out結構中。如果有大量的數據需要進行信息摘要,而且這些數據只有最後幾個字節不同的時候,使用該函數就顯得特別有用,節省時間。其中,out結構必須在調用本函數之前進行初始化。操作成功返回1,否則返回0。
【EVP_MD_CTX_copy】
  該函數跟EVP_MD_CTX_copy_ex函數功能相同,但是out參數可以不用初始化。

3.2.2.2 參數設置與獲取函數

#define EVP_MAX_MD_SIZE 64     /* SHA512 */
int EVP_MD_type(const EVP_MD *md);
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);

const EVP_MD *EVP_MD_CTX_md(const EVP_MD_CTX *ctx);
#define EVP_MD_CTX_size(e)        EVP_MD_size(EVP_MD_CTX_md(e))
#define EVP_MD_CTX_block_size(e)  EVP_MD_block_size((e)->digest)
#define EVP_MD_CTX_type(e)        EVP_MD_type((e)->digest)

const EVP_MD *EVP_get_digestbyname(const char *name);
#define EVP_get_digestbynid(a) EVP_get_digestbyname(OBJ_nid2sn(a))
#define EVP_get_digestbyobj(a) EVP_get_digestbynid(OBJ_obj2nid(a))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

【EVP_MD_size和EVP_MD_CTX_size】
  這兩個函數返回結構裏面摘要信息的長度。

【EVP_MD_block_size和EVP_MD_CTX_block_size】
  這兩個函數返回摘要信息分塊的長度。

【EVP_MD_type和EVP_MD_CTX_type】
  這兩個函數返回信息摘要結構算法的NID。例如,EVP_MD_type(EVP_sha1())返回NID_sha1。該函數通常在設置ASN1 OID的時候使用。如果算法不存在,返回NID_undef。

【EVP_MD_CTX_md】
  該函數返回給定EVP_MD_CTX結構裏面的EVP_MD結構

【EVP_MD_pkey_type】
  該函數返回信息摘要結構裏面公鑰簽名算法的NID。例如,如果EVP_sha1是使用RSA簽名算法,那麼就會返回NID_sha1WithRSAEncryption。

【EVP_md2、EVP_md5、EVP_sha、EVP_sha1、EVP_mdc2和EVP_ripemd160】
  這些函數返回相應名字的EVP_MD結構,它們都使用RSA算法作爲簽名算法。在新的程序裏,一般推薦使用sha1算法。

【EVP_dss和EVP_dss1】
  這兩個函數返回的EVP_MD結構分別使用sha和sha1信息摘要算法,但是簽名算法使用DSS(DSA)。

【EVP_md_null】
  該函數返回的信息摘要結構不作任何事情,返回的摘要信息長度爲0。

【EVP_get_digestbyname、EVP_get_digestbynid和EVP_get_digestbyobj】
  這三個函數分別根據給定的算法名稱、算法NID以及ASN1_OBJECT結構返回一個相應的EVP_MD算法結構。摘要算法在使用之前必須進行初始化,如使用Openssl_add_all_digests進行初始化。如果調用不成功,返回NULL。

3.2.3 摘要算法函數

  所在文件m_*.c。

const EVP_MD *EVP_md_null(void);
const EVP_MD *EVP_md2(void);
const EVP_MD *EVP_md4(void);
const EVP_MD *EVP_md5(void);

const EVP_MD *EVP_sha(void);
const EVP_MD *EVP_sha1(void);
const EVP_MD *EVP_sha224(void);
const EVP_MD *EVP_sha256(void);
const EVP_MD *EVP_sha384(void);
const EVP_MD *EVP_sha512(void);       

const EVP_MD *EVP_dss(void);
const EVP_MD *EVP_dss1(void);
const EVP_MD *EVP_ecdsa(void);
const EVP_MD *EVP_mdc2(void);
const EVP_MD *EVP_ripemd160(void);
const EVP_MD *EVP_whirlpool(void);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.3 用法示例

static int OpenSSL_Digest(  const char *digestname, 
                            const unsigned char *in, int inlen,
                            unsigned char *out, unsigned int *poutlen)
{
    int rv = 0, n = 0;
    char szErr[1024];

    EVP_MD_CTX ctx;
    const EVP_MD *md = NULL;

    /* 初始化摘要計算上下文 */
    EVP_MD_CTX_init(&ctx);

    /* 根據摘要算法名稱(如md5,sha1)獲取摘要對象,使用openssl dgst -h命令可以查看支持的摘要算法名) */
    md = EVP_get_digestbyname(digestname);
    if (NULL == md) {
        fprintf( stderr, "OpenSSL_Digest: Digest for %s is NULL\n", digestname );

        rv = -1;
        goto err;
    }

    /* 初始化摘要算法(如果使用Engine,此時會觸發其實現的EVP_MD->init回調函數) */
    if (!EVP_DigestInit(&ctx, md)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_DigestInit failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -3;
        goto err;
    }

    /**
     * 計算摘要(如果使用Engine,此時會觸發其實現的EVP_MD->update回調函數)
     * 對於連續的數據流,EVP_DigestUpdate一般會被調用多次 
     */
    if (!EVP_DigestUpdate(&ctx, in, inlen)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_DigestUpdate failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -4;
            goto err;
    }

    /* 輸出摘要計算結果(如果使用Engine,此時會觸發其實現的EVP_MD->cleanup回調函數) */
    if (!EVP_DigestFinal(&ctx, out, poutlen)) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Cipher: EVP_DigestFinal failed: \nopenssl return %d, %s\n", n, szErr );

        rv = -5;
            goto err;
    }

err:    
    /* 釋放摘要計算上下文 */
    EVP_MD_CTX_cleanup(&ctx);

    return rv;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

4 非對稱加密

  主要是以 p_開頭的文件。其中:

  •   p_enc.c 封裝了公鑰加密;
  •   p_dec.c 封裝了私鑰解密;
  •   p_lib.c 實現一些輔助函數;
  •   p_sign.c 封裝了簽名函數;
  •   p_verify.c 封裝了驗籤函數;
  •   p_seal.c 封裝了數字信封;
  •   p_open.c 封裝瞭解數字信封。

4.1 基本數據結構EVP_PKEY

#include<openssl/evp.h>

struct evp_pkey_st 
{
    int type;
    int save_type;
    int references;
    const EVP_PKEY_ASN1_METHOD *ameth;
    ENGINE *engine;
    union 
    {
        char *ptr;
        struct rsa_st *rsa;     /* RSA */
        struct dsa_st *dsa;     /* DSA */
        struct dh_st *dh;       /* DH */
        struct ec_key_st *ec;   /* ECC */
    } pkey;
    int save_parameters;
    STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  該結構用來存放非對稱密鑰信息,可以是RSA、DSA、DH 或ECC 密鑰。其中,ptr 用來存放密鑰結構地址,attributes 堆棧用來存放密鑰屬性。

4.2 非對稱加密

  所在文件evp.h、p_enc.c、p_dec.c 。

4.2.1 核心函數

4.2.1.1 加密

int EVP_PKEY_encrypt_old(unsigned char *enc_key,const unsigned char *key, 
                         int key_len, EVP_PKEY *pubk) //RSA公鑰加密 

int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx,unsigned char *out, size_t *outlen,
                     const unsigned char *in, size_t inlen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

【EVP_PKEY_encrypt_ini】
  函數使用密鑰pkey初始化公鑰算法的上下文以進行加密操作。返回1成功,0或負值失敗。特別地,返回值-2表示該公鑰算法不支持該操作。

【EVP_PKEY_encrypt】
  函數使用ctx執行公鑰加密操作。使用in和inlen參數指定要加密的數據。如果out爲NULL,則輸出緩衝區的最大大小寫入outlen參數。如果out不爲NULL,那麼在調用之前,outlen參數應該包含out緩衝區的長度,如果調用成功,則將加密數據寫入out,並將數據寫入Outlen。返回1成功,0或負值失敗。特別地,返回值-2表示該公鑰算法不支持該操作。

【EVP_PKEY_encrypt_old】
  該函數默認調用RSA_public_encrypt使用公鑰加密。

4.2.1.2 解密

int EVP_PKEY_decrypt_old(unsigned char *dec_key, const unsigned char *enc_key, 
                         int enc_key_len,EVP_PKEY *private_key);//RSA私鑰解密

int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx,unsigned char *out, size_t *outlen,
                     const unsigned char *in, size_t inlen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

【EVP_PKEY_decrypt_ini】
  函數使用密鑰pkey初始化公鑰算法的上下文以進行解密操作。返回1成功,0或負值失敗。特別地,返回值-2表示該公鑰算法不支持該操作。

【EVP_PKEY_decrypt】
  函數使用ctx執行公鑰解操作。使用in和inlen參數指定要解密的數據。如果out爲NULL,則輸出緩衝區的最大大小寫入outlen參數。如果out不爲NULL,那麼在調用之前,outlen參數應該包含out緩衝區的長度,如果調用成功,則將解密數據寫入out,並將數據寫入Outlen。返回1成功,0或負值失敗。特別地,返回值-2表示該公鑰算法不支持該操作。

【EVP_PKEY_decrypt_old】
  該函數默認調用RSA_private_encrypt使用私鑰解密。

4.2.2 輔助函數

#include <openssl/evp.h>
EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e);
EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e);
EVP_PKEY_CTX *EVP_PKEY_CTX_dup(EVP_PKEY_CTX *ctx);
void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx);

#include <openssl/evp.h>
int EVP_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int keytype, int optype,int cmd, int p1, void *p2);
int EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type,const char *value);

#include <openssl/rsa.h>
int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *ctx, const EVP_MD *md);

int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad);
int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX *ctx, int len);
int EVP_PKEY_CTX_set_rsa_rsa_keygen_bits(EVP_PKEY_CTX *ctx, int mbits);
int EVP_PKEY_CTX_set_rsa_keygen_pubexp(EVP_PKEY_CTX *ctx, BIGNUM *pubexp);

#include <openssl/dsa.h>
int EVP_PKEY_CTX_set_dsa_paramgen_bits(EVP_PKEY_CTX *ctx, int nbits);

#include <openssl/dh.h>
int EVP_PKEY_CTX_set_dh_paramgen_prime_len(EVP_PKEY_CTX *ctx, int len);
int EVP_PKEY_CTX_set_dh_paramgen_generator(EVP_PKEY_CTX *ctx, int gen);

#include <openssl/ec.h>
int EVP_PKEY_CTX_set_ec_paramgen_curve_nid(EVP_PKEY_CTX *ctx, int nid);

#include <openssl/evp.h>
void EVP_PKEY_CTX_set_cb(EVP_PKEY_CTX *ctx, EVP_PKEY_gen_cb *cb);
EVP_PKEY_gen_cb *EVP_PKEY_CTX_get_cb(EVP_PKEY_CTX *ctx);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

  EVP_PKEY_CTX_new()函數使用pkey和ENGINE e中指定的算法分配公鑰算法上下文。
  EVP_PKEY_CTX_new_id()函數使用由id和ENGINE e指定的算法分配公鑰算法上下文。
  EVP_PKEY_CTX_dup()複製上下文ctx。
  EVP_PKEY_CTX_free()釋放上下文ctx。
  EVP_PKEY_CTX_new(),EVP_PKEY_CTX_new_id(),EVP_PKEY_CTX_dup()返回新分配的EVP_PKEY_CTX結構,如果出現錯誤返回NULL。EVP_PKEY_CTX_free()不返回值。

        #include <openssl/evp.h>

//將pkey所指的EVP_PKEY的密鑰設置爲key所指的密鑰,成功返回1,失敗返回0
        int EVP_PKEY_set1_RSA(EVP_PKEY *pkey,RSA *key);
        int EVP_PKEY_set1_DSA(EVP_PKEY *pkey,DSA *key);
        int EVP_PKEY_set1_DH(EVP_PKEY *pkey,DH *key);
        int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey,EC_KEY *key);

//從pkey所指的EVP_PKEY中獲取對應的密鑰,失敗返回NULL 
        RSA *EVP_PKEY_get1_RSA(EVP_PKEY *pkey);
        DSA *EVP_PKEY_get1_DSA(EVP_PKEY *pkey);
        DH *EVP_PKEY_get1_DH(EVP_PKEY *pkey);
        EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *pkey);

//將pkey所指的EVP_PKEY的密鑰設置爲key所指的密鑰,但pkey釋放時,key也會被釋放成功返回1,失敗返回0
        int EVP_PKEY_assign(EVP_PKEY,int type ,void *key) 
        int EVP_PKEY_assign_RSA(EVP_PKEY *pkey,RSA *key);
        int EVP_PKEY_assign_DSA(EVP_PKEY *pkey,DSA *key);
        int EVP_PKEY_assign_DH(EVP_PKEY *pkey,DH *key);
        int EVP_PKEY_assign_EC_KEY(EVP_PKEY *pkey,EC_KEY *key);

//返回與type匹配的密鑰的類型,EVP_PKEY_RSA, EVP_PKEY_DSA, EVP_PKEY_DH or EVP_PKEY_EC或者NID_undef
        int EVP_PKEY_type(int type);

        int EVP_PKEY_missing_parameters(const EVP_PKEY *pkey);
        int EVP_PKEY_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from);

        int EVP_PKEY_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b);
        int EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b);

        int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer);
        int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen);

         #include <openssl/evp.h>
        int EVP_PKEY_get_default_digest_nid(EVP_PKEY *pkey, int *pnid);

        int EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);
        int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_paramgen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);

        typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx);

        int EVP_PKEY_CTX_get_keygen_info(EVP_PKEY_CTX *ctx, int idx);

        void EVP_PKEY_CTX_set_app_data(EVP_PKEY_CTX *ctx, void *data);
        void *EVP_PKEY_CTX_get_app_data(EVP_PKEY_CTX *ctx);

        EVP_PKEY *EVP_PKEY_new(void);
        void EVP_PKEY_free(EVP_PKEY *key);  

        int EVP_PKEY_print_public(BIO *out, const EVP_PKEY *pkey,int indent, ASN1_PCTX *pctx);
        int EVP_PKEY_print_private(BIO *out, const EVP_PKEY *pkey,int indent, ASN1_PCTX *pctx);
        int EVP_PKEY_print_params(BIO *out, const EVP_PKEY *pkey,int indent, ASN1_PCTX *pctx);

        int EVP_PKEY_sign_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_sign(EVP_PKEY_CTX *ctx,unsigned char *sig, size_t *siglen,
                               const unsigned char *tbs, size_t tbslen);

        int EVP_PKEY_verify_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_verify(EVP_PKEY_CTX *ctx, const unsigned char *sig, size_t siglen,
                               const unsigned char *tbs, size_t tbslen);


        int EVP_PKEY_verify_recover_init(EVP_PKEY_CTX *ctx);
        int EVP_PKEY_verify_recover(EVP_PKEY_CTX *ctx,unsigned char *rout, size_t *routlen,
                               const unsigned char *sig, size_t siglen);

        int EVP_PKEY_get_default_digest_nid(EVP_PKEY *pkey, int *pnid);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

4.2.3 用法示例

int OpenSSL_EncryptEx(EVP_PKEY *pPubKey,
            const unsigned char *data, int data_cb, 
            unsigned char* enc, unsigned int *penc_cb)
{
    int rv = 0, n = 0;
    char szErr[1024];
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pPubKey);
    rv = EVP_PKEY_encrypt(&ctx,enc, data, data_cb, pPubKey);
    if (rv <= 0) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );
        fprintf( stderr, "OpenSSL_Encrypt: EVP_PKEY_encrypt failed: \nopenssl return %d, %s\n", n, szErr );

        rv = n;
        goto enc_ret;
    }

    if (*penc_cb) {
        *penc_cb = rv;
    }

    rv = 0;

enc_ret:
    return rv;
}

int OpenSSL_DecryptEx(EVP_PKEY *pPriKey,
            unsigned char *enc, unsigned int enc_cb,
            unsigned char *data, unsigned int *pdata_cb) 
{
    int rv = 0, n = 0;
    char szErr[1024];
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pPriKey);

    rv = EVP_PKEY_decrypt(&ctx, data, enc, enc_cb, pPriKey);
    if (rv <= 0) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );
        fprintf( stderr, "OpenSSL_Decrypt: EVP_PKEY_decrypt failed: \nopenssl return %d, %s\n", n, szErr );

        rv = n;
        goto enc_ret;
    }

    if (*pdata_cb) {
        *pdata_cb = rv;
    }

    rv = 0;

enc_ret:
    return rv;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

5 BASE64編/解碼

  EVP 提供了base64編碼和解碼的高級接口。 Base 64編碼將二進制數據轉換爲使用字符 A-Z,a-z,0-9,“+”和“/”表示來數據的可打印形式。每3個字節的二進制數據,編碼爲上訴4個字符表示的4字節數據。如果輸入數據長度不是3的倍數,則輸出數據將使用“=”字符在最後填充。

5.1 base64編碼原理

  首先將每三個字節原始2進制數據在一起展開;
  然後6bit分爲一個小組。每個小組前面補兩個0,成爲一個字節。
  把新編碼的每個字節轉爲十進制,根據base64標準轉換表,找到對應的字符。
  如果多了一個字節,則剩餘兩個字節用“=”填充,如果多了兩個字節,則剩餘一個字節用“=”填充。

5.2 基本數據結構

#include<openssl/evp.h>

typedef struct evp_Encode_Ctx_st
{
     /* number saved in a partial encode/decode */
     int num;

     /*
      * The length is either the output line length (in input bytes) or the
      * shortest input line length that is ok.  Once decoding begins, the
      * length is adjusted up each time a longer line is decoded
      */
     int length;
     unsigned char enc_data[80];    //待編碼的數據

     int line_num;   /* number read on current line */
     int expect_nl;
} EVP_ENCODE_CTX;     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

5.3 相關函數

  所在文件evp.h encode.c。

5.3.1 核心函數

5.3.1.1 編碼

#include <openssl/evp.h>

void EVP_EncodeInit(EVP_ENCODE_CTX *ctx);
void EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl,
                      const unsigned char *in, int inl);
void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl);

int  EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

【EVP_EncodeInit】
  初始化ctx以啓動新的編碼操作。無返回值。
【EVP_EncodeUpdate】
  編碼in指向的緩衝區中的inl字節數據。輸出存儲在緩衝區out中,輸出的字節數存儲在outl中。調用者必須確保out指向的緩衝區足夠大以容納輸出數據。只有完整的數據塊(48字節)可以被直接編碼完後並通過函數輸出。任何剩餘的字節都保存在ctx對象中,並通過後續調用EVP_EncodeUpdate()或EVP_EncodeFinal()來處理。要計算所需的輸出緩衝區大小,將inl的值與ctx中保留的未處理數據量相加,並將結果除以48(忽略任何餘數),這給出將要處理的數據塊數。確保輸出緩衝區包含每個塊的65個字節的存儲空間,因爲編碼後每64個字節將附加一個’\n’,outl計算時包含‘\n’,此外每一塊編碼後都會輸出一個‘\0’終結符附加在“\n”後,下一次調用update或者final時將自動消去這個’\0’。可以重複調用EVP_EncodeUpdate()來處理大量的輸入數據。發生錯誤EVP_EncodeUpdate()將outl設置爲0。無返回值。
【EVP_EncodeFinal】
  必須在編碼操作結束時調用EVP_EncodeFinal()。它將處理ctx對象中剩餘的任何部分數據塊。輸出數據將被存儲在out,輸出的數據長度將存儲在* outl中,包含了’\n’。調用者者有責任確保輸出緩衝區足夠大以容納不超過65字節,因爲有額外的‘\0’終結器(即總共66個字節)的輸出數據。,無返回值。
【EVP_EncodeBlock】
  EVP_EncodeBlock()對f中的輸入數據進行編碼,並將其存儲在t中。對於每3字節的輸入,將產生4字節的輸出數據。如果n不能被3整除,則塊被當做最後的數據塊來編碼,並且被填充,使得它總是可被除以4。另外還將添加‘\0’終結符字符。例如,如果提供16字節的輸入數據,則創建24字節的編碼數據,加上NUL終結器的1個字節(即總共25個字節)。從函數輸出的長度包括’\0’。返回編碼的字節數包括’\0’。

5.3.1.2 解碼

#include <openssl/evp.h>

void EVP_DecodeInit(EVP_ENCODE_CTX *ctx);
int  EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl,
                     const unsigned char *in, int inl);
int  EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned
                    char *out, int *outl);

int  EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

【EVP_DecodeInit】
  初始化ctx以開始新的解碼操作。
【EVP_DecodeUpdate】
  解碼in指向的緩衝區中inl字節的數據。輸出存儲在緩衝區中out,輸出的字節數存儲在* outl中。調用者有責任確保out指向的緩衝區足夠大以容納輸出數據。該功能將嘗試在4字節塊中儘可能多地解碼數據。任何空格,換行符或回車符都將被忽略。任何保留在結尾的未處理數據(1,2或3個字節)的部分塊將保留在ctx對象中,並由後續調用EVP_DecodeUpdate()處理。如果遇到非法的base64字符,或者如果在數據中間遇到base64填充字符“=”,則函數返回-1表示錯誤。返回值爲0或1表示數據成功處理。返回值0表示處理的最後輸入數據字符包括base64填充字符“=”,因此預期不會再處理非填充字符數據。對於處理的每4個有效的64位字節(忽略空格,回車符和換行符),將產生3字節的二進制輸出數據或更少(在使用填充字符“=”的數據結尾處)。出錯返回-1,成功返回0或1,如果返回0,則不再期待非base64的編碼字符
【EVP_DecodeFinal】
  必須在解碼操作結束時調用EVP_DecodeFinal()。如果仍然存在任何未處理的數據,那麼輸入數據不能是4的倍數,因此發生錯誤。在這種情況下,函數返回-1。否則,該函數成功返回1。成功返回1,失敗返回-1
【 EVP_DecodeBlock】
   EVP_DecodeBlock()將解碼f中包含的基本64個數據的n個字節的塊,並將結果存儲在t中。任何前導空格將被修剪,如任何尾隨的空格,換行符,回車符或EOF字符。在這樣的修剪之後,f中的數據長度必須除以4.對於每4個輸入字節,將產生3個輸出字節。如果需要,輸出將被填充0位,以確保每4個輸入字節的輸出始終爲3個字節。返回解碼的數據長度,出錯返回-1。

5.3.2 輔助函數

EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void);
void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx);
int EVP_ENCODE_CTX_copy(EVP_ENCODE_CTX *dctx, EVP_ENCODE_CTX *sctx)
int EVP_ENCODE_CTX_num(EVP_ENCODE_CTX *ctx);
  • 1
  • 2
  • 3
  • 4

【EVP_ENCODE_CTX_new】
  分配,初始化並返回要用於encode / decode函數的上下文。成功返回地址,失敗返回NULL。

【VP_ENCODE_CTX_free】
  清理編碼/解碼上下文ctx並釋放分配給它的空間。二進制64位數據的編碼是以48個輸入字節(最後一個塊爲少)的塊執行的。對於每個48字節的輸入塊,編碼64字節的基本64個數據被輸出加上一個附加的換行符(即總共65個字節)。最後一個塊(可能小於48個字節)將爲每3個字節的輸入輸出4個字節。如果數據長度不能被3整除,那麼對於最後的1或2字節的輸入,仍然輸出一個完整的4個字節。同樣也會輸出換行符。 無返回值。

【EVP_ENCODE_CTX_num】
  返回在ctx對象中待處理的尚未編碼或解碼的字節數。

6 應用

6.1 消息驗證碼HMAC

HMAC是基於散列函數的MAC(消息認證碼),即用於消息認證的密鑰哈希函數。

unsigned char *HMAC(const EVP_MD *evp_md, const void *key,int key_len, 
              const unsigned char *d, int n,unsigned char *md, unsigned int *md_len);

void HMAC_CTX_init(HMAC_CTX *ctx);

int HMAC_Init(HMAC_CTX *ctx, const void *key, int key_len,const EVP_MD *md);

int HMAC_Init_ex(HMAC_CTX *ctx, const void *key, int key_len,const EVP_MD *md, ENGINE *impl);

int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, int len);

int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len);

void HMAC_CTX_cleanup(HMAC_CTX *ctx);

void HMAC_cleanup(HMAC_CTX *ctx);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

HMAC是基於散列函數的MAC(消息認證碼),即用於消息認證的密鑰哈希函數。

HMAC()使用哈希函數evp_md和key_len字節長的密鑰鍵計算d字節的消息認證碼。

   它將結果放在md(它必須有空格的哈希函數的輸出,不超過EVP_MAX_MD_SIZE字節)。如果md爲NULL,則將摘要放置在靜態數組中。輸出的大小放在md_len中,除非它爲空。

   evp_md可以是EVP_sha1(),EVP_ripemd160()等

   HMAC_CTX_init()在首次使用前初始化HMAC_CTX。必須調用

HMAC_CTX_cleanup()從HMAC_CTX中刪除密鑰和其他數據,並釋放任何關聯的資源。當不再需要HMAC_CTX時,必須調用它。

   HMAC_cleanup()是HMAC_CTX_cleanup()的別名,用於與0.9.6b的後向兼容性,不推薦使用。

   如果消息未完全存儲在內存中,則可能會使用以下功能:

   HMAC_Init()初始化HMAC_CTX結構以使用hash函數evp_md和key_len字節長的密鑰。它已被棄用,僅適用於與OpenSSL 0.9.6b的向後兼容性。

   HMAC_Init_ex()初始化或重用HMAC_CTX結構以使用函數evp_md和key key。可以是NULL,在這種情況下,現有的一個將被重用。 HMAC_CTX_init()必須在此功能首次使用HMAC_CTX之前被調用。注: HMAC_Init()在以前版本的OpenSSL中存在這種未記錄的行爲 - 在程序中未能切換到HMAC_Init_ex(),這些程序期望它們會導致它們停止工作。

   HMAC_Update()可以重複調用消息的大小塊進行身份驗證(數據中爲len個字節)。

   HMAC_Final()將消息認證碼放在md中,它必須具有用於散列函數輸出的空間。

 # define HMAC_MAX_MD_CBLOCK      128/* largest known is SHA512 */

 struct hmac_ctx_st 
 {
    const EVP_MD *md;
    EVP_MD_CTX md_ctx;
    EVP_MD_CTX i_ctx;
    EVP_MD_CTX o_ctx;
    unsigned int key_length;
    unsigned char key[HMAC_MAX_MD_CBLOCK];
 }/* HMAC_CTX*/;

# define HMAC_size(e)    (EVP_MD_size((e)->md))

void HMAC_CTX_init(HMAC_CTX *ctx);
void HMAC_CTX_cleanup(HMAC_CTX *ctx);

size_t HMAC_size(const HMAC_CTX *e);
HMAC_CTX *HMAC_CTX_new(void);
int HMAC_CTX_reset(HMAC_CTX *ctx);
void HMAC_CTX_free(HMAC_CTX *ctx);



/* deprecated */
# define HMAC_cleanup(ctx) HMAC_CTX_cleanup(ctx)

/* deprecated */
int HMAC_Init(HMAC_CTX *ctx, const void *key, int len, const EVP_MD *md);

int HMAC_Init_ex(HMAC_CTX *ctx, const void *key, int len, const EVP_MD *md, ENGINE *impl);
int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len);
int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len);

unsigned char *HMAC(const EVP_MD *evp_md, const void *key, 
                    int key_len,const unsigned char *d, size_t n, 
                    unsigned char *md,unsigned int *md_len);                                                                                                       

int HMAC_CTX_copy(HMAC_CTX *dctx, HMAC_CTX *sctx);
void HMAC_CTX_set_flags(HMAC_CTX *ctx, unsigned long flags);
const EVP_MD *HMAC_CTX_get_md(const HMAC_CTX *ctx);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

6.2 數字簽名

  所在文件evp.h、p_sign.c、p_verify.c。

6.2.1 簽名

  EVP_Sign系列函數使用的基礎結構跟信息摘要算法使用的基礎結構是一樣的,而且,其前面的兩個操作步驟初始化和數據操作(信息摘要)也跟信息摘要算法是一樣的,唯一不一樣的是最後一步操作,本系列函數做了簽名的工作,而信息摘要系列函數當然就只是簡單的處理完摘要信息了事了。其實這是很容易理解的事情,因爲簽名算法就是在信息摘要之後用私鑰進行簽名的過程。本系列函數定義的如下(openssl/evp.h):

     int EVP_SignInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
     int EVP_SignUpdate(EVP_MD_CTX *ctx, const void *d, unsigned int cnt);
     int EVP_SignFinal(EVP_MD_CTX *ctx,unsigned char *sig,unsigned int *s, EVP_PKEY *pkey);

     void EVP_SignInit(EVP_MD_CTX *ctx, const EVP_MD *type);
     int EVP_PKEY_size(EVP_PKEY *pkey);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

【EVP_SignInit_ex】
  該函數是一個宏定義函數,其實際定義如下:
   #define EVP_SignInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c)
  可見,該函數跟前面敘述的EVP_DigestInit_ex的功能和使用方法是一樣的,都是使用ENGINE參數impl所代表的實現函數功能來設置結構ctx。在調用本函數前,參數ctx一定要經過EVP_MD_CTX_init函數初始化。詳細使用方法參看前面的文章介紹。成功返回1,失敗返回0。

【EVP_SignUpdate】
  該函數也是一個宏定義函數,其實際定義如下:
  #define EVP_SignUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
  該函數使用方法和功能也跟前面介紹的EVP_DigestUpdate函數一樣,將一個cnt字節的數據經過信息摘要運算存儲到結構ctx中,該函數可以在一個相同的ctx中調用多次來實現對更多數據的信息摘要工作。成功返回1,失敗返回0

【EVP_SignFinal】
  該函數跟前面兩個函數不同,這是簽名系列函數跟信息摘要函數開始不同的地方,其實,該函數是將簽名操作的信息摘要結構先調用EVP_MD_CTX_copy_ex函數拷貝一份,然後調用EVP_DigestFinal_ex完成信息摘要工作,然後開始對摘要信息用私鑰pkey調用EVP_PKEY_sign_init 和EVP_PKEY_sign進行簽名,並將簽名信息保存在參數sig裏面。如果參數s不爲NULL,那麼就會將簽名信息數據的長度(單位字節)保存在該參數中,通常寫入的數據是EVP_PKEY_size(key)。:q
  因爲操作的時候是拷貝了一份ctx,所以,原來的ctx結構還可以繼續使用EVP_SignUpdate和EVP_SignFinal函數來完成更多信息的簽名工作。不過,最後一定要使用EVP_MD_CTX_cleanup函數清除和釋放ctx結構,否則就會造成內存泄漏。
  此外,當使用DSA私鑰簽名的時候,一定要對產生的隨機數進行種子播種工作(seeded),否則操作就會失敗。RSA算法則不一定需要這樣做。至於使用的簽名算法跟摘要算法的關係,在EVP_Digest系列中已經有詳細說明,這裏不再重複。
  本函數操作成功返回1,否則返回0。

【EVP_SignInit】
  本函數也是一個宏定義函數,其定義如下:
  #define EVP_SignInit(a,b) EVP_DigestInit(a,b)
  所以其功能和用法跟前面介紹的EVP_DigestInit函數完全一樣,使用缺省實現的算法初始化算法結構ctx。

【EVP_PKEY_size】
  本函數返回一個簽名信息的最大長度(單位字節)。實際簽名信息的長度則由上述的函數EVP_SignFinal返回,有可能比這小。

  上述所有函數發生錯誤,可以使用ERR_get_error()獲取錯誤碼,用ERR_error_String(err_NO,pstr)函數獲得錯誤信息。

6.2.2 認證

  跟EVP_Sign系列函數一樣,EVP_Verify系列函數的前兩步(初始化和信息摘要處理)跟信息摘要算法是一樣的,因爲簽名驗證的過程就是先對信息進行信息摘要,然後再將發來的摘要信息用公鑰解密後進行比較的過程,其定義如下(openssl/evp.h):

     int EVP_VerifyInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
     int EVP_VerifyUpdate(EVP_MD_CTX *ctx, const void *d, unsigned int cnt);
     int EVP_VerifyFinal(EVP_MD_CTX *ctx,unsigned char *sigbuf, unsigned int siglen,EVP_PKEY *pkey);

     int EVP_VerifyInit(EVP_MD_CTX *ctx, const EVP_MD *type);
  • 1
  • 2
  • 3
  • 4
  • 5

【EVP_VerifyInit_ex】
  該函數是一個宏定義函數,其實際定義如下:
   #define EVP_VerifyInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c)
  所以,其功能和使用方法跟前面介紹的EVP_DigestInit_ex函數是一樣的。該函數使用參數impl所提供的算法庫對驗證結構ctx進行設置。在調用本函數之前,參數ctx必須經過調用EVP_MD_CTX_init進行初始化。成功返回1,失敗返回0。
【EVP_VerifyUpdate】
  該函數也是一個宏定義函數,其實際定義如下:
  #define EVP_VerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c)
  所以,其功能和使用方法跟前面介紹的EVP_DigestUpdate函數是相同的。該函數將參數d中的cnt字節數據經過信息摘要計算後保存到ctx中,該函數可以進行多次調用,以處理更多的數據。成功調用返回1,失敗返回0。
【EVP_VerifyFinal】
  該函數使用公鑰pkey和ctx結構裏面的信息驗證sigbuf裏面的數據的簽名。事實上,該函數先調用EVP_MD_CTX_copy_ex函數將原來的ctx拷貝一份,然後調用EVP_DigestFinal_ex函數完成拷貝的ctx的信息摘要計算,最後才使用公鑰pkey調用EVP_PKEY_verify_init 和EVP_PKEY_verify_進行簽名的驗證工作。
  因爲該函數實際上處理的是原來ctx函數的一個拷貝,所以原來的ctx結構還可以調用EVP_VerifyUpdate和EVP_VerifyFinal函數進行更多的數據處理和簽名驗證工作。
  在使用完之後,ctx必須使用EVP_MD_CTX_cleanup函數釋放內存,否則就會導致內存泄漏。
  此外,至於信息摘要算法和簽名算法的關聯的關係,請參照信息摘要算法部分的說明。
  該函數調用成功返回1,失敗則返回0或-1。
【EVP_VerifyInit】
  該函數使用缺省的實現算法對ctx結構進行初始化。也是一個宏定義函數,其定義如下:
  #define EVP_VerifyInit(a,b) EVP_DigestInit(a,b)
  所以跟EVP_DigestInit函數功能和用法是一樣的。

6.2.3 用法示例

static int OpenSSL_Sign(EVP_PKEY *pPriKey, 
            unsigned char *data, int data_cb, 
            unsigned char* sign, unsigned int *psign_cb )
{
    const EVP_MD *md = NULL;
    EVP_MD_CTX md_sign_ctx;
    int nRet = 0, n = 0;
    char szErr[1024];

    //根據密鑰類型選擇摘要算法
    switch (pPriKey->type) {
    case EVP_PKEY_EC:
        md = EVP_ecdsa();
        break;
    default:
        md = EVP_sha1();
        break;
    }

    //摘要上下文初始化
    if( !EVP_SignInit ( &md_sign_ctx, md ) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );
        fprintf( stderr, "OpenSSL_Sign: EVP_SignInit failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -1;
        goto sign_ret;
    }

    //簽名所需的摘要計算,如果有多段數據,可以多次調用EVP_SignUpdate
    if( ! EVP_SignUpdate(&md_sign_ctx, data, data_cb) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr,"OpenSSL_Sign: EVP_SignUpdate failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -2;
        goto sign_ret;
    }

    //計算簽名
    if( !EVP_SignFinal (&md_sign_ctx, 
            sign, 
            psign_cb, 
            pPriKey ) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr,"OpenSSL_Sign: EVP_SignFinal failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -3;
        goto sign_ret;
    }

sign_ret:
    if( !EVP_MD_CTX_cleanup(&md_sign_ctx) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );
        fprintf( stderr,"OpenSSL_Sign: EVP_ctx_cleanup failed: \nopenssl return %d, %s\n", n, szErr );
    }

    return nRet;
}

static int OpenSSL_Verify(EVP_PKEY *pPubKey, 
              unsigned char *data, int data_cb, 
              unsigned char* sign, unsigned int sign_cb )
{
    const EVP_MD *md = NULL;
    EVP_MD_CTX md_sign_ctx, md_verify_ctx;
    int nRet = 0, n = 0;
    char szErr[1024];

    //根據密鑰類型選擇摘要算法
    switch (pPubKey->type) {
    case EVP_PKEY_EC:
        md = EVP_ecdsa();
        break;
    default:
        md = EVP_sha1();
        break;
    }

    //摘要上下文初始化
    if( !EVP_VerifyInit( &md_verify_ctx, md ) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Verify: EVP_VerifyInit failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -4;
        goto verify_ret;
    }

    //驗籤所需的摘要計算,如果有多段數據,可以多次調用EVP_VerifyUpdate
    if( !EVP_VerifyUpdate(&md_verify_ctx, data, data_cb) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Verify: EVP_VerifyUpdate failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -5;
        goto verify_ret;
    }

    //驗證簽名
    if( !EVP_VerifyFinal(&md_verify_ctx, sign, sign_cb, pPubKey) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );

        fprintf( stderr, "OpenSSL_Verify: EVP_VerifyFinal failed: \nopenssl return %d, %s\n", n, szErr );

        nRet = -6;
        goto verify_ret;
    }

verify_ret:
    if( !EVP_MD_CTX_cleanup(&md_verify_ctx) ) {
        n  = ERR_get_error();
        ERR_error_string( n, szErr );
        fprintf( stderr, "OpenSSL_Verify: EVP_ctx_cleanup failed: \nopenssl return %d, %s\n", n, szErr );
    }

    return nRet;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

6.3 數字信封

  所在文件evp.h、p_seal.c 、p_open.c。

6.3.1 寫信

  seal系列函數是相當於完成一個電子信封的功能,它產生一個隨機密鑰,然後使用一個公鑰對該密鑰進行封裝,數據可以使用該隨機密鑰進行對稱加密。
  信封加密在進行大量數據傳輸的時候是必須經常要用到的,因爲公開密鑰算法的加解密速度很慢,但對稱算法就快多了。所以一般用公開密鑰算法對產生的隨機密鑰加密,而真正進行數據加密則使用該隨機密鑰進行對稱加密,然後將加密後的密鑰與數據一起發送
  其定義的函數如下(openssl/evp.h):

     int EVP_SealInit(EVP_CIPHER_CTX *ctx, EVP_CIPHER *type, unsigned char **ek,
                      int *ekl, unsigned char *iv,EVP_PKEY **pubk, int npubk);
     int EVP_SealUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
                        int *outl, unsigned char *in, int inl);
     int EVP_SealFinal(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl);
  • 1
  • 2
  • 3
  • 4
  • 5

【EVP_SealInit】
  該函數初始化一個加密算法結構EVP_CIPHER_CTX,採用了指定的加密算法,使用一個隨機密鑰和初始化向量IV。事實上,該函數調用EVP_EncryptInit_ex函數兩次完成了ctx結構的初始化工作。參數type是算法類型,跟簽名介紹過的是一樣的,爲EVP_des_cbc類型的函數的返回值。隨機密鑰鑰被一個或多個公鑰加密,這就允許祕鑰被公鑰相應的私鑰解密。參數ek是一個緩存序列,可以存放多個被公鑰加密後的密鑰的信息,所以每個緩存空間都應該足夠大,比如ek[i]的緩存空間就必須爲EVP_PKEY_size(pubk[i])那麼大。每個被加密的隨機密鑰的長度保存在數字ekl中。參數pubk是一個公鑰陳列,可以包含多個公鑰。函數成功執行返回npubk,失敗返回0
  因爲該函數的密鑰是隨機產生的,隨意在調用該函數之前,必須對隨機數播種(seeded)。
  使用的公鑰必須是RSA,因爲在openssl裏面這是唯一支持密鑰傳輸的公鑰算法。因爲該函數調用了EVP_PKEY_encrypt_old函數
  跟EVP_EncryptInit函數一樣,本函數也可以分爲兩次調用,第一次調用的時候要將參數npubk設爲0,第二調用的時候就應該將參數type設爲NULL。

【EVP_SealUpdate】
  該函數是一個宏定義函數,其實際定義如下:
  #define EVP_SealUpdate(a,b,c,d,e) EVP_EncryptUpdate(a,b,c,d,e)
  由此可見,其完成的功能和使用方法跟EVP_EncryptUpdate函數是一樣的。細節參看前面介紹的文章。成功執行返回1,否則返回0。

【EVP_SealFinal】
  該函數簡單調用了EVP_EncryptFinal_ex完成其功能,所以其完成的功能和使用參數也跟EVP_EncryptFinal_ex函數一樣,細節請參考相關文章。唯一不一樣的是,該函數還調用EVP_EncryptInit_ex(ctx,NULL,NULL,NULL,NULL)函數對ctx結構再次進行了初始化。成功返回1,否則返回0。

6.3.2 讀信

  本系列函數相對於EVP_Seal系列函數,是進行信封加密的。它將公鑰加密了的密鑰加密出來,然後進行數據的解密。其定義的函數如下(openssl/evp.h):

     int EVP_OpenInit(EVP_CIPHER_CTX *ctx,EVP_CIPHER *type,unsigned char *ek,
                      int ekl,unsigned char *iv,EVP_PKEY *priv);
     int EVP_OpenUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
                       int *outl, unsigned char *in, int inl);
     int EVP_OpenFinal(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl);
  • 1
  • 2
  • 3
  • 4
  • 5

【EVP_OpenInit】
  該函數初始化一個用來加密數據的ctx結構。它首先使用參數priv指定的私鑰解密參數ek裏面長度爲ekl字節的加密密鑰。然後用此密鑰與參數iv指定的初始化向量初始化EVP_CIPHER_CTX。如果參數type設定的加密算法長度是可變的,那麼密鑰長度就會被設置爲解密得到的密鑰的長度;如果加密算法長度是固定的,那麼得到的解密密鑰的長度就必須跟固定算法長度相同才行。成功執行返回密鑰的長度,否則返回0。
  跟函數EVP_DecryptInit一樣,該函數也可以分成多次調用,首次調用應該將參數priv設置爲NULL,再次調用的時候應該將type設置爲NULL。

【EVP_OpenUpdate】
  該函數是一個宏定義函數,其實際定義如下:
  #define EVP_OpenUpdate(a,b,c,d,e) EVP_DecryptUpdate(a,b,c,d,e)
  所以,其功能和使用方法跟前面介紹過的EVP_DecryptUpdate相同,請參考相應的文章。成功執行返回1,否則返回0。

【EVP_OpenFinal】
  事實上,該函數調用EVP_DecryptFinal_ex完成了其功能,所以其使用方法跟功能跟函數EVP_DecryptFinal_ex是一樣的,參考該函數說明就可以。唯一不同的是,本函數還調用EVP_DecryptInit_ex(ctx,NULL,NULL,NULL,NULL)再次進行了初始化工作。成功執行返回1,否則返回0。

6.3.3 用法示例

void TestPKCS7Enc(EVP_PKEY *pPriKey, X509 *x)
{
    int rv = 0;
    char szErr[1024] = {0};

    STACK_OF(X509) *certs = sk_X509_new_null();
    unsigned char data[32] = {0};
    unsigned int data_cb = sizeof(data);
    unsigned char enc[8192] = {0};
    unsigned int enc_len = sizeof(enc);

    unsigned int dec_len = sizeof(enc);
    unsigned char *p = NULL;

    PKCS7 *p7 = NULL;
    BIO *in = BIO_new_mem_buf(data, data_cb);
    BIO *out = BIO_new(BIO_s_mem());

    RAND_pseudo_bytes(data, data_cb);
    BIO_dump_fp(stdout, data, data_cb);

    sk_X509_push(certs, x);
    p7 = PKCS7_encrypt(certs, in, EVP_des_cbc(), PKCS7_BINARY);
    if (NULL == p7) {
        rv  = ERR_get_error();
        ERR_error_string(rv, szErr);
        fprintf( stderr, "TestPKCS7Enc: PKCS7_encrypt failed: \nopenssl return %d, %s\n", rv, szErr );

        rv = -1;
        goto err;
    }

    p = enc;
    enc_len = i2d_PKCS7(p7, &p);

    BIO_dump_fp(stdout, enc, enc_len);

    if (!PKCS7_decrypt(p7, pPriKey, x, out, PKCS7_BINARY)) {
        rv  = ERR_get_error();
        ERR_error_string(rv, szErr);
        fprintf( stderr, "TestPKCS7Enc: PKCS7_decrypt failed: \nopenssl return %d, %s\n", rv, szErr );

        rv = -1;
        goto err;
    }

    p = NULL;
    dec_len = BIO_get_mem_data(out, &p);
    BIO_dump_fp(stdout, p, dec_len);

err:
    if (p7) {
        PKCS7_free(p7);
        p7 = NULL;
    }

    if (in) {
        BIO_free(in);
        in = NULL;
    }

    if (out) {
        BIO_free(out);
        in = NULL;
    }

    if (certs) {
        sk_X509_free(certs);
        certs = NULL;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章