0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【openssl】從openssl的常用接口淺談【內(nèi)存泄漏】

嵌入式物聯(lián)網(wǎng)開發(fā) ? 來源:嵌入式物聯(lián)網(wǎng)開發(fā) ? 作者:嵌入式物聯(lián)網(wǎng)開發(fā) ? 2022-08-31 11:24 ? 次閱讀

?

openssl是一個很有名的開源軟件,它在解決SSL/TLS通訊上提供了一套行之有效的解決方案,同時在軟件算法領(lǐng)域,它也集成絕大部分常見的算法,真可謂是程序員開發(fā)網(wǎng)絡通訊和信息安全加解密的一個利器。

熟悉github的朋友,一定在github上目睹過openssl的真容【https://github.com/openssl/openssl】,它的官網(wǎng)地址是【/index.html】。就拿github來說,高達8.8K顆星,被fork4千多次,總共有2萬多次的提交記錄,足以可見該開源項目的熱度有多高。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?編輯

然而,就是這樣的一個開源利器,能給我們工作帶來便利的同時,倘若你使用不當,那么給你帶來的不是喜悅,而是煩惱。通過觀察openssl提供的API,你會發(fā)現(xiàn),它的很多API返回的都是指針類型,在應用層調(diào)用時,僅需用一個對應類型的指針去接收返回的指針,即可取得對應的數(shù)據(jù)或操作方法,使用非常靈活。類似這樣的接口有很多,例如:

//新生成一個BIGNUM結(jié)構(gòu)
BIGNUM *BN_new(void);

//將s中的len位的正整數(shù)轉(zhuǎn)化為大數(shù)
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); 
 
//初始化一個RSA結(jié)構(gòu)
RSA * RSA_new(void);
 
//RSA私鑰產(chǎn)生函數(shù)
//產(chǎn)生一個模為num位的密鑰對,e為公開的加密指數(shù),一般為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

聰明的你,留意到這些“生成”功能的API接口的同時,一定也留意到它們都有對應的“銷毀”API接口。上面列表一一對應的是:

//釋放一個BIGNUM結(jié)構(gòu),釋放完后a=NULL;
void BN_free(BIGNUM *a);

//釋放一個RSA結(jié)構(gòu)
void RSA_free(RSA *rsa);
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

看到這里,也許你就會明白我今天要講的主題了,既然這些“生成”API提供了返回指針類型的功能,那么很明顯指針所指向內(nèi)容的存儲空間,必定是在openssl內(nèi)部通過malloc等動態(tài)內(nèi)存申請的方式獲取的;所以在使用了這段內(nèi)存后,自然而然就是要執(zhí)行內(nèi)存釋放的動作,這與C語言動態(tài)內(nèi)存申請講的“malloc--free”必須配套使用,是如出一轍的;只不過,現(xiàn)在這些openssl的API是把malloc和free的動作封裝在了接口的內(nèi)部,而暴露給調(diào)用者的只有類型XXX_init和XXX_free,或者XXX_new和XXX_delete,諸如此類的接口,僅此而已。

回到openssl的API接口的使用上來,博主有一次在使用openssl的某個接口有些疑惑,想在網(wǎng)上找找調(diào)用的demo時,結(jié)果一搜,一眼就進到 【OpenSSL編程-RSA編程詳解 http://www.qmailer.net/archives/216.html】這篇博文。它確實給初學者提供了幾組常用API的簡單demo,正常情況下這些代碼都是能跑通的,但近來我在日常工作中,有在做一些【內(nèi)存泄露】相關(guān)的【代碼優(yōu)化】工作,所以對這個切入點比較敏感,果不其然,細讀其中的部分示例代碼,就發(fā)現(xiàn)了其中不嚴謹?shù)牡胤?,很有可能就會發(fā)生【內(nèi)存泄露】的風險。如果本身系統(tǒng)的內(nèi)存比較吃緊,比如像在嵌入式系統(tǒng)上運行,這樣的【內(nèi)存泄露】可以說是致命的。

還是拿代碼來說事,以下代碼片段是上文提及的參考博文中截取到的,如下:

1. 數(shù)據(jù)加、密解密示例

#include
#include
#include
#include
#include
#include
 
#define PRIKEY "prikey.pem"
#define PUBKEY "pubkey.pem"
#define BUFFSIZE 4096
 
/************************************************************************
 * RSA加密解密函數(shù)
 *
 * file: test_rsa_encdec.c
 * gcc -Wall -O2 -o test_rsa_encdec test_rsa_encdec.c -lcrypto -lssl
 *
 * author: tonglulin@gmail.com 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;  //函數(shù)出口1
    }
 
    /* 讀取公鑰PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函數(shù) */
    if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
        return NULL; //函數(shù)出口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; //函數(shù)出口3
    }
 
    RSA_free(rsa);
    fclose(fp);
 
    return en; //函數(shù)出口4
}
 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

通過簡單分析,我們可以知道m(xù)y_encrypt這個函數(shù),有一個入口,但是有4個出口(見代碼注釋):

函數(shù)出口1: 很明顯出錯的可能性是,打開pubkey_path文件失敗,這個很好理解,可能是文件不存在,或者路徑文件不正確等等,此處出錯,對外返回NULL,是完全沒有問題的。

函數(shù)出口2: 這里出錯的可能性是fp指向的pubkey_path文件,壓根不是一個pem格式的公鑰文件,自然會出錯;但是此處直接對外返回NULL,而不管fp的死活,這是不可取的!

函數(shù)出口3: 這里出錯的可能是公鑰加密輸入的數(shù)據(jù)長度不對或者數(shù)據(jù)填充不對等等,然而這里也是出錯后,立即對外返回NULL,完全不理fp和rsa,還有en這條友【往往更容易忽略】,這3者的死活,更是不可取的!

函數(shù)出口4: 這個沒的說,正常的函數(shù)出口;大家注意,在這個正常的函數(shù)出口中,它在return前是執(zhí)行了RSA_free(rsa); fclose(fp); 的動作;沒錯,這個就是我們要講的使用完的內(nèi)存要及時釋放,它的使用需要注意的關(guān)鍵點就在這里。那么如上可能出現(xiàn)內(nèi)存泄露的代碼片段應該如何優(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語句,保證函數(shù)單一入口,單一出口
    }
 
    /* 讀取公鑰PEM,PUBKEY格式PEM使用PEM_read_RSA_PUBKEY函數(shù) */
    if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) == NULL) {
        return NULL;
        goto exit_entry; //使用goto語句,保證函數(shù)單一入口,單一出口
    }
 
    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申請不到內(nèi)存的時候,也不能往下執(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理論上已經(jīng)不為空了,當在這一步出錯時,對外en的內(nèi)存已經(jīng)變得無意義了,所以必須要釋放掉,同時將en置為NULL,防止外部調(diào)用者邏輯出錯
            en = NULL;
        }
        goto exit_entry;
    }
 
exit_entry: //函數(shù)統(tǒng)一出口,退出前執(zhí)行相應的內(nèi)存釋放動作

    //先判斷是否需要執(zhí)行內(nèi)存釋放
    if (rsa) {
        RSA_free(rsa);
    }
    //文件打開的fp句柄要及時關(guān)閉
    if (fp) {
        fclose(fp);
    } 

    return en;
}
 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

通過如上的示例代碼,基本上很好地修復了因異常情況處理不當導致的【內(nèi)存泄露】隱患,同時利用goto語句,使得函數(shù)的結(jié)構(gòu)的緊湊性有所提高,代碼的可讀性也提升了不少。

有的朋友可能會有疑問,“我們在學C語言教程的時候,老師不是常常跟我們說,盡量不要使用goto語句,這樣會帶來代碼災難,為何博主卻推薦使用goto語句來優(yōu)化代碼呢?”

原因很簡單,C語言的goto語句并不會造成代碼災難,而是使用goto語句的程序員造成的災難!怎么說呢,goto語句是有點偏匯編層面的關(guān)鍵字,它有點像匯編指令里面的jump指令,也就是說使用好它,指不定還可以提升代碼的運行效率。但是值得注意的是,goto語句不能濫用,尤其是使用goto語句往前跳轉(zhuǎn),或者使用goto語句執(zhí)行遞歸、循環(huán)等操作時,代碼的邏輯將有可能變得不可控制,或者難以控制,基本上除了寫代碼本身的人能讀懂外【估計過個一兩個月,他自己也讀不懂了】,其他人估計就摸不著頭腦了。但是,如果像用在如上所優(yōu)化的代碼那樣,僅僅在函數(shù)的出口做一個symbol標簽,當函數(shù)中間執(zhí)行異常的時候,立即跳轉(zhuǎn)到定義的出口標簽,同時執(zhí)行一些函數(shù)退出的收尾工作,比如清理內(nèi)存、釋放不再使用的內(nèi)存、接口返回值轉(zhuǎn)換等操作;這樣的代碼,將會大大提升了代碼的可讀性,這也盡可能地將錯誤規(guī)避掉,讓bug無處藏身,代碼更加整潔,反而能編寫出可讀性強的高質(zhì)量代碼。

如上僅是提出了對【內(nèi)存泄露】的小小看法和感悟,借助openssl的demo例子,也僅僅是拋磚引玉,也許讀者有更高的見解。期待有讀者與我一同討論相關(guān)話題。

注:文中引用了【博主:大佟,文章地址:http://www.qmailer.net/archives/216.html】的示例代碼,如有版權(quán)問題,請及時與我聯(lián)系。不勝感激!

?審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8598

    瀏覽量

    151153
  • 開源
    +關(guān)注

    關(guān)注

    3

    文章

    3349

    瀏覽量

    42499
  • OpenSSL
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    8691
收藏 人收藏

    評論

    相關(guān)推薦

    嵌入式學習-ElfBoard ELF 1板卡-移植openssl

    openssl是一個開放源代碼的軟件庫包,應用程序可以使用這個包來進行安全通信,避免竊聽,同時確認另一端連線者的身份。這個包被廣泛應用于互聯(lián)網(wǎng)的網(wǎng)頁服務器上。下面這篇文章就給各位小伙伴介紹一下如何在
    發(fā)表于 12-28 08:53

    Openssl怎樣移植到ARM開發(fā)板

    Openssl移植到ARM開發(fā)板openssl源碼下載源碼編譯配置使用生成的庫文件openssl源碼下載openssl源碼可以在官網(wǎng)下載:https://www.
    發(fā)表于 07-01 07:46

    openssl庫移植過程記錄

    openssl庫移植1 什么是openssl可以這么說,只要你的應用程序和網(wǎng)絡安全有關(guān)系,不管是http還是mqtt,那么都需要依賴到openssl;首先openssl是一個開源庫,功
    發(fā)表于 11-04 06:43

    openssl有哪些應用呢

    1、環(huán)境配置在me.h文件里修改以下宏定義ME_COM_MBEDTLS 0ME_COM_MATRIXSSL 0ME_COM_NANOSSL 0ME_COM_OPENSSL 12、添加
    發(fā)表于 12-16 07:41

    i.MX8MP開發(fā)板中移植OpenSSL工具

    也是圍繞這三個功能部分進行規(guī)劃的。作為一個基于密碼學的安全開發(fā)包,OpenSSL提供的功能相當強大和全面,囊括了主要的密碼算法、常用的密鑰和證書封裝管理功能以及SSL協(xié)議,并提供了豐富的應用程序供測試
    發(fā)表于 08-25 10:04

    OpenSSL的SE05x是否支持openssl主機加密并積極測試?

    設置經(jīng)過身份驗證的會話并觀察持續(xù)的內(nèi)存泄漏。通過 valgrind 深入研究,我在 `sss_host_mac_init()` 中發(fā)現(xiàn)了內(nèi)存泄漏。然后我能夠確定
    發(fā)表于 04-10 08:08

    OpenSSL在電子商務安全中的應用

    基于公共密鑰技術(shù)的SSL 協(xié)議已經(jīng)成為Internet 上廣泛應用的安全通信標準,源碼開放的OpenSSL是對SSL 協(xié)議的完整實現(xiàn)。介紹了OpenSSL 在電子商務安全中的應用。
    發(fā)表于 08-31 15:58 ?12次下載

    如何才能使用OpenSSL實現(xiàn)一個基本的安全連接的詳細概述

    OpenSSL API 的文檔有些含糊不清。因為還沒有多少關(guān)于 OpenSSL 使用的教程,所以對初學者來說,在 應用程序中使用它可能會有一些困難。那么怎樣才能使用 OpenSSL 實現(xiàn)一個基本的安全連接呢? 本教程將幫助您解決
    的頭像 發(fā)表于 05-27 10:26 ?6097次閱讀
    如何才能使用<b class='flag-5'>OpenSSL</b>實現(xiàn)一個基本的安全連接的詳細概述

    密碼學OpenSSL的入門基礎知識整理合集

    本文是使用 OpenSSL 的密碼學基礎知識的兩篇文章中的第一篇,OpenSSL 是在 Linux 和其他系統(tǒng)上流行的生產(chǎn)級庫和工具包。(要安裝 OpenSSL 的最新版本,請參閱 這里
    的頭像 發(fā)表于 02-07 15:29 ?3691次閱讀
    密碼學<b class='flag-5'>OpenSSL</b>的入門基礎知識整理合集

    嵌入式Linux系統(tǒng)openssl庫移植

    openssl庫移植1 什么是openssl可以這么說,只要你的應用程序和網(wǎng)絡安全有關(guān)系,不管是http還是mqtt,那么都需要依賴到openssl;首先openssl是一個開源庫,功
    發(fā)表于 11-01 16:31 ?8次下載
    嵌入式Linux系統(tǒng)<b class='flag-5'>openssl</b>庫移植

    openssl】利用openssl命令行快速生成RSA私鑰

    openssl】如何利用openssl命令行快速生成RSA私鑰?
    的頭像 發(fā)表于 08-31 12:58 ?3365次閱讀
    【<b class='flag-5'>openssl</b>】利用<b class='flag-5'>openssl</b>命令行快速生成RSA私鑰

    如何修補 Rust 中即將出現(xiàn)的OpenSSL漏洞

    OpenSSL將在 11 月 1 日披露一個新的嚴重漏洞并發(fā)布補丁版本。 為了保護您的 Rust 程序,您需要做的就是更新系統(tǒng)范圍內(nèi)的 OpenSSL 安裝。
    的頭像 發(fā)表于 10-31 09:46 ?1151次閱讀

    飛凌i.MX8MP開發(fā)板OpenSSL的使用方法

    在OKMX8MP-C開發(fā)板中,飛凌移植了OpenSSL工具, 作為一個基于密碼學的安全開發(fā)包,OpenSSL提供的功能相當強大和全面,囊括了主要的密碼算法、常用的密鑰和證書封裝管理功能以及SSL協(xié)議
    的頭像 發(fā)表于 08-25 15:19 ?721次閱讀
    飛凌i.MX8MP開發(fā)板<b class='flag-5'>OpenSSL</b>的使用方法

    為什么嵌入式系統(tǒng)設計人員應該關(guān)注OpenSSL

      首先,一些基礎知識。比如OpenSSL。常用的安全套接字層 (SSL) 提供了 Web 服務器和瀏覽器之間的加密鏈接技術(shù),而 OpenSSL 則為保護網(wǎng)絡通信的應用程序提供了加密庫。它可以處理消息摘要、隨機數(shù)、數(shù)字證書和簽名
    的頭像 發(fā)表于 06-29 10:25 ?530次閱讀

    OpenSSL安裝常見問題

    OpenSSL安裝常見問題
    的頭像 發(fā)表于 07-07 11:17 ?907次閱讀
    <b class='flag-5'>OpenSSL</b>安裝常見問題