文章目錄
上一部分介紹了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
,就可以將數據打印到屏幕上。
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結構體稱爲對象
對稱加密和解密的流程類似,一般有以下幾個步驟:
- 生成一個記錄加密(解密)上下文信息的
EVP_CIPHER_CTX
對象 - 初始化加密(解密)算法,在這一步指定算法和密鑰
- 加密(解密)數據
- 處理尾部數據,結束加密(解密)
- 清空並釋放加密(解密)上下文對象,清空其他敏感信息
其中使用的函數以及其他一些相關函數如下:
頭文件
#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
表示失敗。
示例
我寫了一個加密和解密文件的小例子,有興趣的朋友可以看一下, 包含主要加密解密流程的操作在代碼中的encrypt
和decrypt
函數中。
https://gitee.com/JanuaryJIAN/codes/2dhv13rw5bpmntzijlu9c32#0-qzone-1-66526-d020d2d2a4e8d1a374a433f596ad1440