version | date | commit-msg |
---|---|---|
1.0 | 2019/7/30 | first commit |
在平常的開發中,涉及到加解密庫的開發總離不開對OpenSSL接口的調用,筆者借對公司內部加解密庫進行國密算法擴充的契機,對 OpenSSL進行了一番學習與實踐😀。git
所以本文將介紹EVP接口的使用,並給出SM2公鑰加密的具體實現。github
OpenSSL提供了一系列的函數用於特定加解密算法,好比,可使用#include "rsa.h"
中的RSA_public_encrypt()
完成RSA公鑰加密計算。 可是,OpenSSL還提供了一種統一接口,開發者可經過調用統一接口,使得只須要在初始化參數的時候作不多的改變,就可使用相同的代碼但採用不一樣的加密算法進行數據的加密和解密[1],這種統一接口就是本文要介紹的EVP接口。算法
EVP接口封裝了摘要算法,密鑰生成,對稱加解密,非對稱加解密,驗證等功能,其中比較常見的函數有:函數
EVP接口提供了對稱加解密函數,其中有專門進行加密的函數,在進行加密以前,首先初始化加密上下文,而後調用加密函數進行加密計算。學習
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
複製代碼
#include "evp.h"
/* * @param ctx: 這表明加解密的上下文 * @param type: type表明須要使用的算法類型。 好比填寫EVP_sm4_cbc()則使用了CBC分組模式的SM4算法。 在evp中預先定義了多種type可供開發者選用 * @param impl:ENGINE表明了加解密的引擎。 OpenSSL是支持自定義加解密引擎的,即自定義加解密算法的具體實現而 不使用默認的OpenSSL實現。 通常狀況下,咱們都使用OpenSSL中的加解密算法,所以可置爲NULL * @param key: 密鑰 * @param iv: 初始向量,若是選擇了ECB,則不須要iv */
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, const unsigned char *key, const unsigned char *iv);
EVP_EncryptUpdate()
EVP_EncryptFinal_ex()
複製代碼
#include "evp.h"
/* * @param out: 密文 * @param outl: 密文長度 * @param in: 明文 * @param inl: 明文長度 */
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
複製代碼
#include "evp.h"
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
複製代碼
EVP提供了對應的解密函數,參數設置與加密函數相似,用法也很相似,接口以下:加密
#include "evp.h"
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, 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 *outm, int *outl);
複製代碼
EVP更是提供了加密和解密的統一接口,該接口惟一的區別就是在初始化的時候,經過enc參數斷定是加密模式仍是解密模式。spa
#include "evp.h"
/* * @param enc: enc==1表明加密,enc==0表明解密 */
int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, 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 *outm, int *outl);
複製代碼
進行公鑰加密的重點在於設置密鑰類型。在OpenSSL中,存在多種類型的密鑰和一個統一的EVP_PKEY
密鑰,須要經過輔助函數將特定密鑰轉化成統一密鑰。.net
特定密鑰有:code
ec.h
頭文件中的EC_KEY
表明了橢圓曲線的密鑰rsa.h
頭文件中的RSA
表明RSA密鑰DH
密鑰和DSA
密鑰等這些特定的密鑰可使用輔助函數進行設置:htm
int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, EC_KEY *key);
將EC_KEY
設置到EVP_PKEY
中int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key);
將RSA
設置到EVP_PKEY
中在OpenSSL1.0.*
版本中,外部是能夠引用#include "sm2.h"
的。🤣那是一個美好的田園時光,網上大量的對sm2算法的調用都經過 sm2.h
頭文件中定義的sm2_*
函數進行。當時筆者須要對公共加密庫進行sm2擴充的時候,苦苦搜尋sm2.h
而不得,最後才發如今OpenSSL1.1*
版本已經再也不對外暴露sm2.h
,所以在OpenSSL1.1*
中只能徹底經過 evp.h
調用了。
首先是密鑰的生成,在生成密鑰階段,就須要告訴OpenSSL生成SM2密鑰。
EC_KEY* key = EC_KEY_new();
/* * OpenSSL內置了許多曲線,所以須要設置使用哪條曲線。NID_sm2 */
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
EC_KEY_set_group(key, group);
// 根據曲線,生成密鑰
EC_KEY_generate_key(key);
// 可使用該函數對生成的密鑰進行檢查
EC_KEY_check_key((const EC_KEY*)key)
複製代碼
爲完成非對稱公鑰加密,須要依次調用如下這些函數:
// 初始化公鑰上下文
1. EVP_PKEY *p_key = EVP_PKEY_new();
// 將EC_KEY結構中存儲的密鑰保存到EVP_PKEY中,並設置EVP_PKEY的類型爲橢圓曲線密鑰
2. EVP_PKEY_set1_EC_KEY(p_key, key);
// 設置密鑰類型爲SM2密鑰,而非其餘的什麼密鑰
3. EVP_PKEY_set_alias_type(p_key, EVP_PKEY_SM2);
// 初始化加密上下文,須要傳入密鑰。 第二個參數爲ENGINE,同上,可置爲NULL
4. EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(p_key, NULL);
// 初始化加密上下文
5. EVP_PKEY_encrypt_init(ctx);
// 使用公鑰進行加密
6. EVP_PKEY_encrypt(ctx, *ciphertext, ciphertext_len, plaintext, plain_len);
複製代碼
以上步驟比較重要的是第3步。OpenSSL內置了一些橢圓曲線,所以須要開發者顯示的指定使用哪條曲線,所以在密鑰中指定使用SM2,最終會在初始化加密上下文的時候,告訴OpenSSL具體的算法,從而在加密階段使用SM2算法。
摘要函數的調用須要使用到4個函數便可:
// 初始化計算上下文
1. EVP_MD_CTX* ctx = EVP_MD_CTX_new();
// 初始化摘要計算,其中type表明須要使用的摘要算法。好比EVP_sm3()使用sm3摘要
2. int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
3. int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
4. int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
複製代碼