EVP_CIPHER_CTX加解密接口函數說明

源碼文件:/evp/evp_enc.c

EVP_CIPHER_CTX ctx;

加密函數說明:

1、EVP_CIPHER_CTX_init(&ctx)

把ctx清0

2、EVP_EncryptInit_ex(&ctx, EVP_rc4(), NULL, key, NULL);

初始化ctx
2.1、ctx->encrypt = enc;設置ctx爲加密或者解密
2.2、ctx->cipher = cipher;設置ctx的加密算法爲EVP_rc4()
2.3、ctx->cipher_data = OPENSSL_malloc(ctx->cipher->ctx_size);開闢算法的私有數據空間,並賦值給ctx,這個私有數據一般是祕鑰,在這裏是EVP_RC4_KEY。
2.4、根據算法設置ctx的其它一些成員
ctx->key_len = cipher->key_len;
ctx->flags = 0;
2.5、ctx->cipher->init(ctx, key, iv, enc) 把key設置到2.3所分配的cipher_data中
2.6、最後設置一些ctx成員
ctx->buf_len = 0;
ctx->final_used = 0;
ctx->block_mask = ctx->cipher->block_size - 1; 加密塊掩碼用例檢測要加密的數據是否是各算法塊大小的整數倍

3、EVP_EncryptUpdate()

3.1、如果要加密的數據大小是算法塊大小的整數倍執行如下的if分支。直接調用EVP算法加密函數進行加密

if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {

*outl = 0;

if (ctx->cipher->do_cipher(ctx, out, in, inl)) {

*outl = inl;

return 1;

} else {

*outl = 0;

return 0;

}

}

 

3.2、如果要加密的數據大小不是算法塊大小的整數倍,先只加密塊大小最大整數倍大小的數據。
i = inl & (bl - 1); //計算要加密的數據大小除以塊大小的餘數
inl -= i;//要加密的數據最後不夠塊大小的數據暫時不加密
if (inl > 0) {
if (!ctx->cipher->do_cipher(ctx, out, in, inl))
return 0;
*outl += inl;
}
3.3、memcpy(ctx->buf, &(in[inl]), i);把剩餘不夠塊大小沒加密完的數據存到ctx裏面
ctx->buf_len = i; 沒加密完的數據大小也記錄下來,在EVP_EncryptFinal_ex會用
3.4、在執行完EVP_EncryptUpdate()後,outl大小分三種情況:
3.4.1、outl = 0   要加密的數據大小不足塊大小
3.4.2、outl = inl 要加密的數據大小大於塊大小且是塊大小的整數倍,這種情況下outl就是要加密的數據長度
3.4.3、outl = inl - (inl % block_size) 要加密的數據大小大於塊大小但不是塊大小的整數倍,這種情況下outl是要加密的數據長度減去最後不夠塊大小的數據長度

4、EVP_EncryptFinal_ex(&ctx, zz + i, &j)

一般用來處理最後沒加密完的數據,這裏的zz是要加密的數據,i是EVP_EncryptUpdate()處理完後的outl。
4.1、bl = ctx->buf_len;這個長度是0或者是沒加密的數據長度

4.2、如果bl不是0,並且加密模式被設置過EVP_CIPH_NO_PADDING,返回0(表示加密失敗)。

 如果明文大小是塊大小的整數倍,outl設置爲0,就返回1(表示最後執行成功),這種情況下沒必要再執行EVP_EncryptFinal_ex函數

if (ctx->flags & EVP_CIPH_NO_PADDING) {

if (bl) {

EVPerr(EVP_F_EVP_ENCRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);

return 0;

}

*outl = 0;

return 1;//EVP_EncryptFinal_ex()只對需要填充的模式起效

}

4.3、對最後的數據填充至算法block_size大小
n = b - bl; //b= ctx->cipher->block_size;算法的塊大小。bl大小見4.1的說明
for (i = bl; i < b; i++)

ctx->buf[i] = n; 

buf[EVP_MAX_BLOCK_LENGTH]這裏的buf是固定32字節的一個數組,

也就是說openssl只支持最多32字節的塊大小,如需要支持更大塊大小,需要修改代碼。

可以看出openssl採用的是pkcs7的填充規則(如果有n個字節需要填充,那麼每個字節就填入n,十六進制填充)。

4.4、對最後一個填充過的塊進行加密

ret = ctx->cipher->do_cipher(ctx, out, ctx->buf, b);//加密成功後,密文的大小一定是算法塊大小的整數倍。

 

 


解密函數說明:
5、EVP_DecryptInit_ex和EVP_EncryptInit_ex是一樣的,都是掉用的EVP_CipherInit_ex()函數,EVP_CipherInit_ex的最後一個參數是1還是0來設置ctx是加密還是解密
6、EVP_DecryptUpdate

 

 

6.1、if (ctx->flags & EVP_CIPH_NO_PADDING)

 

return EVP_EncryptUpdate(ctx, out, outl, in, inl);

不填充,直接解密。這裏加解密都是調用的EVP_EncryptUpdate(),具體是加密還是解密是根據ctx->encrypt的值來判斷的,在2.1中ctx初始化時設置這個成員

這個時候執行EVP_EncryptUpdate()中如下代碼:

if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {

if (ctx->cipher->do_cipher(ctx, out, in, inl)) {

//解密成功

*outl = inl;

return 1;

}

*outl = 0;

return 0;

}
不填充,密文長度是塊大小的整數倍,解密才成功。如果填充,不執行這段代碼。
6.2、EVP_EncryptUpdate(ctx, out, outl, in, inl)執行解密
6.2.1、如果要解密的數據大小是塊大小的整數倍,執行6.1中執行的EVP_EncryptUpdate()代碼

6.2.2、如果要解密的數據大小不是塊大小的整數倍,執行EVP_EncryptUpdate(),這時調的是具體算法的解密函數。

和加密類似,解密時最後一個不夠塊大小的密文塊不會被解密,存放到ctx->buf裏面,同時設置ctx->buf_len = i,XXX_final函數會用到

正常情況下,密文是不會出現大小不是塊大小的整數倍的。

 

6.3、處理最後一個密文塊,也就是明文填充後加密出的那個密文塊

if (b > 1 && !ctx->buf_len) {

//正常情況下會走這個分支,正常情況是指,密文是由相應的加密算法成功加密的結果,不是隨意的一個字符串。

*outl -= b;//需要單獨處理最後一個填充過的塊,除最後一個填充過的塊,其它數據塊跟原來的明文是一樣的。

ctx->final_used = 1;

memcpy(ctx->final, &out[*outl], b); //把解密後最後一個帶填充值的數據塊,複製到ctx->final_used裏面

}

7、EVP_DecryptFinal,這個函數直接調用的EVP_DecryptFinal_ex(),並無其它操作

8、EVP_DecryptFinal_ex

8.1、如果是不填充的,並且還有沒解密完的數據,報錯,返回解密失敗0。

如果是不填充的,並且密文長度是塊大小的整數倍,什麼都沒做就返回了,這種情況下就沒必要再調EVP_DecryptFinal了。

if (ctx->flags & EVP_CIPH_NO_PADDING) {

if (ctx->buf_len) { 

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);

return 0;

}

*outl = 0;

return 1;

}

8.2、如何還有沒解密完的數據塊,直接返回,報錯。密文不正確、不是塊大小的整數倍纔會出現沒解密完的數據塊。

if (ctx->buf_len || !ctx->final_used) {

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_WRONG_FINAL_BLOCK_LENGTH);

return (0);

}

 

8.3、從填充數據的最後一個字節獲取填充的值,並檢測獲取到的值是否合法

n = ctx->final[b - 1];

if (n == 0 || n > (int)b) {

//填充的值是不能爲0的,也不能大於算法的塊大小

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT); 

return (0);

}

for (i = 0; i < n; i++) {

//循環檢查填充的值是否正確,檢測規則是:根據最後一個字節的值n確定最後的幾個字節需要檢查,並且每個字節的值等於n。

if (ctx->final[--b] != n) {

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT);

return (0); 

}

}

8.4、剔除填充的值

//計算填充塊中,明文的長度

n = ctx->cipher->block_size - n;

for (i = 0; i < n; i++)

out[i] = ctx->final[i];   //把明文放到out裏面,這個out是EVP_DecryptUpdate()執行完後的out+outl1

*outl = n;

這樣在應用程序調用EVP_DecryptFinal()這個函數後,這個outl的值就是最後一個填充過的數據塊中原來明文的長度。這個outl(假設爲outl2)和EVP_DecryptUpdate()處理完成後的outl(假設爲outl1)合起來就是原來明文的長度,

其實完整的明文在調用EVP_DecryptUpdate()後已經全部解密出來了(只是需要確定最後一個填充塊中明文的長度)

最後掉用EVP_DecryptFinal()用來確定填充塊中的明文的長度。

EVP_DecryptUpdate()執行完成後的out就是解密後明文的起始地址,outl1+outl2就是解密後的明文長度

 

密文一定要是對應算法塊大小的整數倍,在調用解密的EVP_XXX函數之前可以做個檢查,不滿足這個條件可以不用解密了
不填充,只能加密塊大小整數倍的明文,否則,加密結果是不完整的

 

 

9、關於設置是否填充的接口

 

 

EVP_CIPHER_CTX_set_padding(ctx, flag)

flag是0表示沒有padding,flag是1表示有padding。默認是有padding的結果

EVP加密步驟:
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx);

EVP_EncryptInit_ex(&ctx, ec, NULL, key, NULL);

 

//padding爲0表示不填充,1表示填充,這裏會根據padding的值來設置ctx->flags的值。不調此函數跟padding是1的效果一樣

EVP_CIPHER_CTX_set_padding(&ctx, padding);

EVP_EncryptUpdate(&ctx, out, &out_len, input, input_len); 

input是明文,input_len是明文長度;out是密文,out_len是密文長度

如果明文不是算法塊大小的整數倍,這裏的out_len是不包含最後一個填充塊加密出的密文長度。
EVP_EncryptFinal_ex(&ctx, out + out_len, &out_len_final); //加密最後一個填充的數據塊,out+out_len作用是確定加密後的密文放置位置。最後一個需要填充的明文塊被放置在ctx->buf裏面
本函數在填充後會調用算法的加密函數進行加密,並把加密出的密文放到out+out_len處。
如果明文大小是塊大小的整數倍,可以不調本函數。這種情況下ctx->buf是空的,這個函數不會有下一步的加密操作
如果明文大小是塊大小的整數倍,調用了本函數,並且是填充模式,那麼就會填充整個塊大小(也就是整個數據塊都是填充的值,並不包含明文)的數據來進行加密,這其實是不需要的
從out開始的out_len+out_len_final長的字符就是完整的密文

加密完成後的密文不能直接在網絡上發送。直接寫文件也是亂碼,也不合適。需要做BASE64轉換後在發往網絡或者寫文件。以下是用lcrypto庫提供的BASE64轉換接口實現的BASE64轉換。

int base64_encode(char *in_str, int in_len, char *out_str)
{
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;

    if (in_str == NULL || out_str == NULL)
        return -1;
    
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);
    
    BIO_write(bio, in_str, in_len);
    BIO_flush(bio);
    
    BIO_get_mem_ptr(bio, &bptr);
    memcpy(out_str, bptr->data, bptr->length);
    out_str[bptr->length] = '\0';
    size = bptr->length;
    
    BIO_free_all(bio);
    return size;
}       

int base64_decode(char *in_str, int in_len, char *out_str)
{   
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    int counts;
    int size = 0;
    
    if (in_str == NULL || out_str == NULL)
        return -1;
    
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    
    bio = BIO_new_mem_buf(in_str, in_len);
    bio = BIO_push(b64, bio);

    size = BIO_read(bio, out_str, in_len);
    out_str[size] = '\0';
 
    BIO_free_all(bio);
    return size;
}


EVP解密步驟
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx)
EVP_DecryptInit_ex(&ctx, ec, NULL, key, NULL);

EVP_CIPHER_CTX_set_padding(&ctx, padding);

//input是密文,input_len是密文長度;out,是明文,out_len是明文長度

EVP_DecryptUpdate(&ctx, out, &out_len, input, input_len);  
如果密文是沒填充過的,那麼out_len就是完整明文的長度,否則是明文的部分長度
這一步裏面明文已經完全解密出來放到out裏面了,只是在有填充時,還不確定最後一個塊裏面有幾個字節是明文EVP_DecryptFinal_ex(&ctx, out + out_len, &out_len_final);從最後一個包含填充值的明文塊裏計算剩餘的明文長度,並把剩餘的明文長度賦值給out_len_final

從out開始的out_len+out_len_final長的字符就是完整的明文

上面兩個例子中的out空間要夠存放加密或解密的值。這個空間大小在加密時,可設置成明文長度+算法的塊大小;解密時,可設置成密文長度。

如果是不填充的
加密時,只能加密長度是塊大小整數倍的明文,否則加密的結果是不完整的。
解密時,EVP_DecryptUpdate()執行後的out包含的字符就是解密後的內容,這個時候out_len始終是0,不能用來確定解密後的明文長度。不需要再調用EVP_DecryptFinal_ex(),調的話也是執行失敗

如果是填充的
不管明文長度是否是塊大小的整數倍,加解密都要調用EVP_EncryptFinal_ex()

發佈了5 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章