?
openssl是一個很有名的開源軟件,它在解決SSL/TLS通訊上提供了一套行之有效的解決方案,同時在軟件算法領域,它也集成絕大部分常見的算法,真可謂是程序員開發(fā)網絡通訊和信息安全加解密的一個利器。
熟悉github的朋友,一定在github上目睹過openssl的真容【https://github.com/openssl/openssl】,它的官網地址是【/index.html】。就拿github來說,高達8.8K顆星,被fork4千多次,總共有2萬多次的提交記錄,足以可見該開源項目的熱度有多高。
![](https://file.elecfans.com//web2/M00/66/20/poYBAGMO1IeAIExZAAEcoWkuCOk815.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6dWxsYw==,size_16,color_FFFFFF,t_70)
![poYBAGDYdXCAWkKMAAAAK8RNs4s030.png](https://file.elecfans.com/web2/M00/03/FB/poYBAGDYdXCAWkKMAAAAK8RNs4s030.png)
?編輯
然而,就是這樣的一個開源利器,能給我們工作帶來便利的同時,倘若你使用不當,那么給你帶來的不是喜悅,而是煩惱。通過觀察openssl提供的API,你會發(fā)現,它的很多API返回的都是指針類型,在應用層調用時,僅需用一個對應類型的指針去接收返回的指針,即可取得對應的數據或操作方法,使用非常靈活。類似這樣的接口有很多,例如:
//新生成一個BIGNUM結構
BIGNUM *BN_new(void);
//將s中的len位的正整數轉化為大數
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
//初始化一個RSA結構
RSA * RSA_new(void);
//RSA私鑰產生函數
//產生一個模為num位的密鑰對,e為公開的加密指數,一般為65537(0x10001)
RSA *RSA_generate_key(int num, unsigned long e,void (*callback)(int,int,void *), void *cb_arg);
//從文件中加載RSAPublicKey格式公鑰證書
RSA *PEM_read_RSAPublicKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u);
//從BIO重加載RSAPublicKey格式公鑰證書
RSA *PEM_read_bio_RSAPublicKey(BIO *bp, RSA **x, pem_password_cb *cb, void *u);
![poYBAGDYdXCAWkKMAAAAK8RNs4s030.png](https://file.elecfans.com/web2/M00/03/FB/poYBAGDYdXCAWkKMAAAAK8RNs4s030.png)
聰明的你,留意到這些“生成”功能的API接口的同時,一定也留意到它們都有對應的“銷毀”API接口。上面列表一一對應的是:
//釋放一個BIGNUM結構,釋放完后a=NULL;
void BN_free(BIGNUM *a);
//釋放一個RSA結構
void RSA_free(RSA *rsa);
![poYBAGDYdXCAWkKMAAAAK8RNs4s030.png](https://file.elecfans.com/web2/M00/03/FB/poYBAGDYdXCAWkKMAAAAK8RNs4s030.png)
看到這里,也許你就會明白我今天要講的主題了,既然這些“生成”API提供了返回指針類型的功能,那么很明顯指針所指向內容的存儲空間,必定是在openssl內部通過malloc等動態(tài)內存申請的方式獲取的;所以在使用了這段內存后,自然而然就是要執(zhí)行內存釋放的動作,這與C語言動態(tài)內存申請講的“malloc--free”必須配套使用,是如出一轍的;只不過,現在這些openssl的API是把malloc和free的動作封裝在了接口的內部,而暴露給調用者的只有類型XXX_init和XXX_free,或者XXX_new和XXX_delete,諸如此類的接口,僅此而已。
回到openssl的API接口的使用上來,博主有一次在使用openssl的某個接口有些疑惑,想在網上找找調用的demo時,結果一搜,一眼就進到 【OpenSSL編程-RSA編程詳解 http://www.qmailer.net/archives/216.html】這篇博文。它確實給初學者提供了幾組常用API的簡單demo,正常情況下這些代碼都是能跑通的,但近來我在日常工作中,有在做一些【內存泄露】相關的【代碼優(yōu)化】工作,所以對這個切入點比較敏感,果不其然,細讀其中的部分示例代碼,就發(fā)現了其中不嚴謹的地方,很有可能就會發(fā)生【內存泄露】的風險。如果本身系統(tǒng)的內存比較吃緊,比如像在嵌入式系統(tǒng)上運行,這樣的【內存泄露】可以說是致命的。
還是拿代碼來說事,以下代碼片段是上文提及的參考博文中截取到的,如下:
1. 數據加、密解密示例
#include
#include
#include
#include
#include
#include
#define PRIKEY "prikey.pem"
#define PUBKEY "pubkey.pem"
#define BUFFSIZE 4096
/************************************************************************
* RSA加密解密函數
*
* file: test_rsa_encdec.c
* gcc -Wall -O2 -o test_rsa_encdec test_rsa_encdec.c -lcrypto -lssl
*
* author: [email protected] by www.qmailer.net
************************************************************************/
char *my_encrypt(char *str, char *pubkey_path)
{
RSA *rsa = NULL;
FILE *fp = NULL;
char *en = NULL;
int len = 0;
int rsa_len = 0;
if ((fp = fopen(pubkey_path, "r")) == NULL) {
return NULL; //函數出口1
}
/* 讀取公鑰PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函數 */
if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
return NULL; //函數出口2
}
RSA_print_fp(stdout, rsa, 0);
len = strlen(str);
rsa_len = RSA_size(rsa);
en = (char *)malloc(rsa_len + 1);
memset(en, 0, rsa_len + 1);
if (RSA_public_encrypt(rsa_len, (unsigned char *)str, (unsigned char*)en, rsa, RSA_NO_PADDING) < 0) {
return NULL; //函數出口3
}
RSA_free(rsa);
fclose(fp);
return en; //函數出口4
}
![poYBAGDYdXCAWkKMAAAAK8RNs4s030.png](https://file.elecfans.com/web2/M00/03/FB/poYBAGDYdXCAWkKMAAAAK8RNs4s030.png)
通過簡單分析,我們可以知道m(xù)y_encrypt這個函數,有一個入口,但是有4個出口(見代碼注釋):
函數出口1: 很明顯出錯的可能性是,打開pubkey_path文件失敗,這個很好理解,可能是文件不存在,或者路徑文件不正確等等,此處出錯,對外返回NULL,是完全沒有問題的。
函數出口2: 這里出錯的可能性是fp指向的pubkey_path文件,壓根不是一個pem格式的公鑰文件,自然會出錯;但是此處直接對外返回NULL,而不管fp的死活,這是不可取的!
函數出口3: 這里出錯的可能是公鑰加密輸入的數據長度不對或者數據填充不對等等,然而這里也是出錯后,立即對外返回NULL,完全不理fp和rsa,還有en這條友【往往更容易忽略】,這3者的死活,更是不可取的!
函數出口4: 這個沒的說,正常的函數出口;大家注意,在這個正常的函數出口中,它在return前是執(zhí)行了RSA_free(rsa); fclose(fp);
的動作;沒錯,這個就是我們要講的使用完的內存要及時釋放,它的使用需要注意的關鍵點就在這里。那么如上可能出現內存泄露的代碼片段應該如何優(yōu)化呢?直接貼上,優(yōu)化后的示例代碼:
char *my_encrypt(char *str, char *pubkey_path)
{
RSA *rsa = NULL;
FILE *fp = NULL;
char *en = NULL;
int len = 0;
int rsa_len = 0;
if ((fp = fopen(pubkey_path, "r")) == NULL) {
en = NULL;
goto exit_entry; //使用goto語句,保證函數單一入口,單一出口
}
/* 讀取公鑰PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函數 */
if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
return NULL;
goto exit_entry; //使用goto語句,保證函數單一入口,單一出口
}
RSA_print_fp(stdout, rsa, 0);
len = strlen(str);
rsa_len = RSA_size(rsa);
en = (char *)malloc(rsa_len + 1);
if (!en) {
goto exit_entry; //當en申請不到內存的時候,也不能往下執(zhí)行了,需要退出
}
memset(en, 0, rsa_len + 1);
if (RSA_public_encrypt(rsa_len, (unsigned char *)str, (unsigned char*)en, rsa, RSA_NO_PADDING) < 0) {
if (en) {
free(en); //走到這里的時候en理論上已經不為空了,當在這一步出錯時,對外en的內存已經變得無意義了,所以必須要釋放掉,同時將en置為NULL,防止外部調用者邏輯出錯
en = NULL;
}
goto exit_entry;
}
exit_entry: //函數統(tǒng)一出口,退出前執(zhí)行相應的內存釋放動作
//先判斷是否需要執(zhí)行內存釋放
if (rsa) {
RSA_free(rsa);
}
//文件打開的fp句柄要及時關閉
if (fp) {
fclose(fp);
}
return en;
}
![poYBAGDYdXCAWkKMAAAAK8RNs4s030.png](https://file.elecfans.com/web2/M00/03/FB/poYBAGDYdXCAWkKMAAAAK8RNs4s030.png)
通過如上的示例代碼,基本上很好地修復了因異常情況處理不當導致的【內存泄露】隱患,同時利用goto語句,使得函數的結構的緊湊性有所提高,代碼的可讀性也提升了不少。
有的朋友可能會有疑問,“我們在學C語言教程的時候,老師不是常常跟我們說,盡量不要使用goto語句,這樣會帶來代碼災難,為何博主卻推薦使用goto語句來優(yōu)化代碼呢?”
原因很簡單,C語言的goto語句并不會造成代碼災難,而是使用goto語句的程序員造成的災難!怎么說呢,goto語句是有點偏匯編層面的關鍵字,它有點像匯編指令里面的jump指令,也就是說使用好它,指不定還可以提升代碼的運行效率。但是值得注意的是,goto語句不能濫用,尤其是使用goto語句往前跳轉,或者使用goto語句執(zhí)行遞歸、循環(huán)等操作時,代碼的邏輯將有可能變得不可控制,或者難以控制,基本上除了寫代碼本身的人能讀懂外【估計過個一兩個月,他自己也讀不懂了】,其他人估計就摸不著頭腦了。但是,如果像用在如上所優(yōu)化的代碼那樣,僅僅在函數的出口做一個symbol標簽,當函數中間執(zhí)行異常的時候,立即跳轉到定義的出口標簽,同時執(zhí)行一些函數退出的收尾工作,比如清理內存、釋放不再使用的內存、接口返回值轉換等操作;這樣的代碼,將會大大提升了代碼的可讀性,這也盡可能地將錯誤規(guī)避掉,讓bug無處藏身,代碼更加整潔,反而能編寫出可讀性強的高質量代碼。
如上僅是提出了對【內存泄露】的小小看法和感悟,借助openssl的demo例子,也僅僅是拋磚引玉,也許讀者有更高的見解。期待有讀者與我一同討論相關話題。
注:文中引用了【博主:大佟,文章地址:http://www.qmailer.net/archives/216.html】的示例代碼,如有版權問題,請及時與我聯系。不勝感激!
?審核編輯:湯梓紅
-
接口
+關注
關注
33文章
8706瀏覽量
151973 -
開源
+關注
關注
3文章
3409瀏覽量
42724 -
OpenSSL
+關注
關注
0文章
21瀏覽量
8746
發(fā)布評論請先 登錄
相關推薦
嵌入式學習-ElfBoard ELF 1板卡-移植openssl
Openssl怎樣移植到ARM開發(fā)板
openssl庫移植過程記錄
openssl有哪些應用呢
i.MX8MP開發(fā)板中移植OpenSSL工具
OpenSSL的SE05x是否支持openssl主機加密并積極測試?
OpenSSL在電子商務安全中的應用
如何才能使用OpenSSL實現一個基本的安全連接的詳細概述
![如何才能使用<b class='flag-5'>OpenSSL</b>實現一個基本的安全連接的詳細概述](https://file.elecfans.com/web1/M00/51/8B/pIYBAFsKGFiATawFAAA1_gOvQbo715.png)
密碼學OpenSSL的入門基礎知識整理合集
![密碼學<b class='flag-5'>OpenSSL</b>的入門基礎知識整理合集](https://file.elecfans.com/web1/M00/B3/EF/pIYBAF49Er6AOR8BAAFuB2omr_0131.png)
嵌入式Linux系統(tǒng)openssl庫移植
![嵌入式Linux系統(tǒng)<b class='flag-5'>openssl</b>庫移植](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
如何修補 Rust 中即將出現的OpenSSL漏洞
飛凌i.MX8MP開發(fā)板OpenSSL的使用方法
![飛凌i.MX8MP開發(fā)板<b class='flag-5'>OpenSSL</b>的使用方法](https://file.elecfans.com/web2/M00/64/FF/pYYBAGMHHq6AVXriAABzdhtGH2k100.png)
評論