Android Native C/C++ 使用OpenSSL EVP介面

BB8發表於2019-07-30
version date commit-msg
1.0 2019/7/30 first commit

在日常的開發中,涉及到加解密庫的開發總離不開對OpenSSL介面的呼叫,筆者借對公司內部加解密庫進行國密演算法擴充的契機,對 OpenSSL進行了一番學習與實踐?。

因此本文將介紹EVP介面的使用,並給出SM2公鑰加密的具體實現。

概述

OpenSSL提供了一系列的函式用於特定加解密演算法,比如,可以使用#include "rsa.h"中的RSA_public_encrypt()完成RSA公鑰加密計算。 但是,OpenSSL還提供了一種統一介面,開發者可通過呼叫統一介面,使得只需要在初始化引數的時候做很少的改變,就可以使用相同的程式碼但採用不同的加密演算法進行資料的加密和解密[1],這種統一介面就是本文要介紹的EVP介面。

EVP介面

EVP介面封裝了摘要演算法,金鑰生成,對稱加解密,非對稱加解密,驗證等功能,其中比較常見的函式有:

使用EVP介面完成對稱加解密

EVP介面提供了對稱加解密函式,其中有專門進行加密的函式,在進行加密之前,首先初始化加密上下文,然後呼叫加密函式進行加密計算。

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
複製程式碼
  1. 初始化對稱加密
#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()
複製程式碼
  1. 對明文進行分塊加密
#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);
複製程式碼
  1. 對明文塊的最後一塊進行處理,因為有填充的存在,因此需要對最後一塊進行特殊處理。
#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引數判定是加密模式還是解密模式。

#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);

複製程式碼

使用EVP進行非對稱金鑰生成與公鑰加密

進行公鑰加密的重點在於設定金鑰型別。在OpenSSL中,存在多種型別的金鑰和一個統一的EVP_PKEY金鑰,需要通過輔助函式將特定金鑰轉化成統一金鑰。

特定金鑰有:

  • ec.h標頭檔案中的EC_KEY代表了橢圓曲線的金鑰
  • rsa.h標頭檔案中的RSA代表RSA金鑰
  • 還有DH金鑰和DSA金鑰等

這些特定的金鑰可以使用輔助函式進行設定:

  • 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

使用SM2公鑰加密

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演算法。

使用EVP介面完成摘要函式

摘要函式的呼叫需要使用到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);
複製程式碼

References

相關文章