openssl基礎(二)密碼庫的使用


  上一部分介紹了openssl的部分命令行用法,但很多時候我麼還需要在程序中使用openssl,這裏主要介紹了使用openssl的密碼庫進行對稱密鑰加密的相關知識。

約定

在沒有特殊說明的情況下,本文提到的長度指的是字節數目

1. 數據輸出

頭文件

#include <openssl/bio.h>

函數

int BIO_dump_fp(FILE *fp, const char *s, int len);

該函數以16進制+字符形式(如下圖所示)打印數據到fp中, s爲數據所在地址,len爲長度。將fp指定爲stdio,就可以將數據打印到屏幕上。
BIO_dump_fp輸出結果

stdio是定義在頭文件stdio.h中的一個FILE*變量,表示標準輸出

2. 錯誤處理

頭文件

#include <openssl/err.h>

函數

void ERR_print_errors_fp(FILE *fp);

該函數將openssl的上一條錯誤信息打印到fp中,將fp指定爲stderr,就可以將數據打印到屏幕上。

stderr是定義在頭文件stdio.h中的一個FILE*變量,表示標準錯誤輸出

3. 對稱加密

雖然c語言沒有對象,爲了方便描述, 我這裏把struct結構體稱爲對象

對稱加密和解密的流程類似,一般有以下幾個步驟:

  1. 生成一個記錄加密(解密)上下文信息的EVP_CIPHER_CTX對象
  2. 初始化加密(解密)算法,在這一步指定算法和密鑰
  3. 加密(解密)數據
  4. 處理尾部數據,結束加密(解密)
  5. 清空並釋放加密(解密)上下文對象,清空其他敏感信息
    其中使用的函數以及其他一些相關函數如下:

頭文件

#include <openssl/evp.h>

上下文處理

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);

創建新加密上下文EVP_CIPHER_CTX對象, 並將其作爲返回值返回

void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);

清除並釋放加密上下文對象(防止數據泄露),參數爲需要釋放的EVP_CIPHER_CTX對象,在所有加密操作結束後調用該函數

int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx);

目前不是很清楚具體作用,可能是重置一個EVP_CIPHER_CTX對象從而可以循環利用避免不必要的內存釋放和分配吧

加解密初始化

加密

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, const unsigned char *key, const unsigned char *iv);

該函數對加密操作進行初始化,參數描述如下:

參數 描述
ctx 加密上下文對象
type 加密算法類型,在openssl/evp.h中定義了許多以算法命名的函數
這些函數的返回值作爲此參數使用,比如EVP_aes_256_cbc()
impl 利用硬件加密的接口,本文不討論,設置爲NULL
key 用於加密的密鑰
iv 某些加密模式如cbc需要使用的初始化向量,如果加密模式不需要可以設置爲NULL

返回值爲1表示成功0表示失敗,可以使用上述錯誤處理中的函數打印錯誤信息

解密

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, const unsigned char *key, const unsigned char *iv);

該函數對解密操作進行初始化,參數與返回值上述加密初始化函數描述相同

執行加解密操作

注意, 輸出緩衝區的長度需要比輸入緩衝區大一個加密塊,否則會出現錯誤。

注意,如果出現overlap錯誤,請檢查輸入和輸出緩衝區是否分離,以及是否其長度是否滿足第一個注意事項

加密

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, const unsigned char *in, int inl);

執行加密的函數,參數描述如下:

參數 描述
ctx 加密上下文對象
out 保存輸出結果(密文)的緩衝區
outl 接收輸出結果長度的指針
in 包含輸入數據(明文)的緩衝區
inl 輸入數據的長度

返回值爲1表示成功,返回值爲0表示失敗

解密

int EVP_DecryptUpdate(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);

該函數處理加密結果的尾部數據(比如填充段塊),還可能輸出一些密文數據,參數描述如下:

參數 描述
ctx 加密上下文對象
out 保存輸出結果(密文)的緩衝區
注意這個指針要指向之前已經保存的加密數據的尾部
outl 接收輸出結果長度的指針

返回值爲1表示成功,0表示失敗。

解密

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);

該函數處理解密結果的尾部數據,還可能輸出一些明文數據,參數和返回值同上述加密尾部數據處理的函數類似,注意這個函數輸出的是明文即可

資源釋放

在加解密操作完成後,對可能的密碼緩衝區的清空,以及釋放上下文對象,一般使用上下文處理中的

void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);

釋放上下文對象即可

4. 口令生成密鑰(key derivation)

有時候我們需要使用口令來生成加密密鑰,openssl推薦使用PBKDF2算法來進行這個操作,使用到的函數如下。

關於PBKDF2的描述參考維基百科PBKDF或者RFC2898(PBKDF2)

頭文件

#include <openssl/evp.h>

函數

int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
                   const unsigned char *salt, int saltlen, int iter,
                   const EVP_MD *digest,
                   int keylen, unsigned char *out);

該函數使用PKKDF2算法利用口令生成指定長度的密鑰,其參數描述如下:

參數 描述
pass 用於生成密鑰的口令
passlen 口令的長度
salt 用於生成密鑰的鹽值(建議4字節以上),當然也可以設置爲NULL表示不使用
saltlen 鹽值的長度,如果不使用則爲0
iter 迭代次數(openssl建議設置到1000以上,用於增加暴力破解的難度)
digest 單向hash函數,在openssl/evp.h中定義了許多以算法命名的函數
這些函數的返回值作爲此參數使用,比如EVP_sha256()
keylen 輸出的密鑰的長度
out 保存輸出的密鑰的緩衝區

返回值爲1表示成功,0表示失敗。

示例

我寫了一個加密和解密文件的小例子,有興趣的朋友可以看一下, 包含主要加密解密流程的操作在代碼中的encryptdecrypt函數中。

https://gitee.com/JanuaryJIAN/codes/2dhv13rw5bpmntzijlu9c32#0-qzone-1-66526-d020d2d2a4e8d1a374a433f596ad1440

參考資料

openssl manpage

openssl wiki

openssl1.1.1d源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章