C++中如何實(shí)現(xiàn)SSL/TLS加密通信
概述
在互聯(lián)網(wǎng)時(shí)代,數(shù)據(jù)的安全性變得尤為重要。隨著網(wǎng)絡(luò)安全威脅的不斷增加,確保信息傳輸過(guò)程中的機(jī)密性、完整性和可用性成為了開發(fā)者必須考慮的關(guān)鍵因素。在C++網(wǎng)絡(luò)編程中,使用SSL/TLS加密通信是一種常見的做法。它允許客戶端和服務(wù)器之間通過(guò)互聯(lián)網(wǎng)安全地交換信息,從而為網(wǎng)絡(luò)通信提供隱私性和數(shù)據(jù)完整性。
SSL,英文全稱為Secure Sockets Layer,最初由Netscape公司在1990年代開發(fā),用于保護(hù)Web瀏覽器與服務(wù)器間的通信。TLS,英文全稱為Transport Layer Security,是IETF標(biāo)準(zhǔn)化后的版本,可以看作是SSL的繼承者。盡管名字不同,但兩者提供的功能非常相似,通常會(huì)把它們統(tǒng)稱為“SSL/TLS”。
基本概念
SSL/TLS:一種用于在兩個(gè)通信應(yīng)用程序之間提供保密性和數(shù)據(jù)完整性的協(xié)議。
證書:一種數(shù)字文檔,包含了一個(gè)實(shí)體的信息及其公鑰。它由一個(gè)可信賴的第三方機(jī)構(gòu)(CA,即Certificate Authority)簽發(fā),以證明該實(shí)體的身份。
公鑰/私鑰: 每個(gè)參與者都有一對(duì)密鑰,主要用于非對(duì)稱加密算法。公鑰公開給所有人,用于加密或驗(yàn)證簽名。私鑰則保密保存,僅用于解密或創(chuàng)建簽名。這保證了即使數(shù)據(jù)被攔截,沒(méi)有私鑰也無(wú)法讀取其內(nèi)容。非對(duì)稱加密算法的安全性在于:計(jì)算上難以從公鑰推導(dǎo)出私鑰。常見的非對(duì)稱加密算法包括:RSA、ECC等。
會(huì)話密鑰:一旦客戶端與服務(wù)器建立了信任關(guān)系,就會(huì)生成一個(gè)臨時(shí)的對(duì)稱密鑰,來(lái)進(jìn)行后續(xù)的數(shù)據(jù)加密和解密工作。對(duì)稱加密算法因?yàn)槠浼用芙饷芩俣瓤欤诖罅繑?shù)據(jù)傳輸中非常有用。常用的對(duì)稱加密算法有:AES、DES等。
公鑰/私鑰與會(huì)話密鑰之間的主要關(guān)系在于:安全地建立對(duì)稱加密會(huì)話。具體來(lái)說(shuō),有如下兩個(gè)主要步驟。
1、使用非對(duì)稱加密來(lái)安全地交換會(huì)話密鑰。比如:小王可以使用小張的公鑰加密一個(gè)會(huì)話密鑰,并將它發(fā)送給小張;只有擁有相應(yīng)私鑰的小張,才能解密該會(huì)話密鑰。
2、一旦會(huì)話密鑰被安全地交換,小王和小張就可以使用這個(gè)會(huì)話密鑰,并通過(guò)對(duì)稱加密算法來(lái)加密實(shí)際傳輸?shù)臄?shù)據(jù)。
API接口
OpenSSL是一個(gè)開源軟件包,提供了豐富的函數(shù)用于實(shí)現(xiàn)SSL/TLS加密通信,一些常用的API如下。
SSL_CTX_new:創(chuàng)建一個(gè)新的SSL上下文。
SSL_CTX_use_certificate_file:用于設(shè)置證書文件路徑。
SSL_CTX_use_PrivateKey_file:用于設(shè)置私鑰文件路徑。
SSL_new:根據(jù)給定的SSL上下文創(chuàng)建一個(gè)新的SSL結(jié)構(gòu)體實(shí)例。
SSL_set_fd:將已有的socket描述符綁定至SSL對(duì)象上。
SSL_connect:客戶端調(diào)用此函數(shù)開始SSL握手過(guò)程。
SSL_accept:服務(wù)端調(diào)用此函數(shù)開始SSL握手過(guò)程。
SSL_read:用于讀取加密后的數(shù)據(jù)流。
SSL_write:用于寫入加密后的數(shù)據(jù)流。
SSL_shutdown:發(fā)起關(guān)閉SSL連接的過(guò)程。
使用OpenSSL庫(kù)進(jìn)行SSL/TLS加密通信的主要步驟如下。
1、初始化OpenSSL庫(kù)。通常情況下,我們需要調(diào)用SSL_library_init等函數(shù)來(lái)初始化環(huán)境。
2、創(chuàng)建SSL上下文。使用SSL_CTX_new創(chuàng)建一個(gè)新的SSL_CTX對(duì)象,并配置相關(guān)的選項(xiàng)。比如:選擇使用的協(xié)議版本為TLSv1.2、TLSv1.3等。
3、加載證書文件。如果作為服務(wù)端運(yùn)行,則需要加載自己的證書及私鑰。如果是客戶端,則可能需要指定CA證書列表,以驗(yàn)證服務(wù)器的身份。
4、建立普通套接字連接。與普通的TCP/IP編程一樣,先完成兩臺(tái)機(jī)器之間的基本連接。
5、將普通套接字轉(zhuǎn)換成SSL套接字。通過(guò)SSL_set_fd函數(shù),關(guān)聯(lián)已有的Socket句柄與SSL結(jié)構(gòu)體。
6、握手。雙方交換各自的信息,并協(xié)商加密算法、生成共享的密鑰等。這一過(guò)程,通過(guò)SSL_connect或SSL_accept函數(shù)完成。
7、數(shù)據(jù)傳輸。使用SSL_read和SSL_write代替標(biāo)準(zhǔn)的read和write操作,以確保所有發(fā)送和接收的數(shù)據(jù)都是加密的。
8、關(guān)閉連接。正常情況下,應(yīng)先調(diào)用SSL_shutdown()進(jìn)行優(yōu)雅斷開,之后再關(guān)閉底層socket。
在C++中如何進(jìn)行SSL/TLS加密通信,可參考下面服務(wù)端的代碼。
#include <iostream> #include <openssl/ssl.h> #include <openssl/err.h> #include <string> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <cstdlib> using namespace std; int main() { if (OPENSSL_init_crypto(0, nullptr) != 1) { cout << "Failed to initialize OpenSSL crypto library." << endl; exit(EXIT_FAILURE); } if (OPENSSL_init_ssl(0, nullptr) != 1) { cout << "Failed to initialize OpenSSL SSL library." << endl; exit(EXIT_FAILURE); } // 使用最新的TLS版本 const SSL_METHOD *method = TLS_server_method(); SSL_CTX *ctx = SSL_CTX_new(method); if (ctx == nullptr) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 配置服務(wù)器證書 if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 配置服務(wù)器私鑰 if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 檢查私鑰是否匹配證書 if (!SSL_CTX_check_private_key(ctx)) { cout << "Private key does not match the certificate public key." << endl; exit(EXIT_FAILURE); } // 開啟監(jiān)聽 int serverSock = socket(AF_INET, SOCK_STREAM, 0); if (serverSock < 0) { cout << "Failed to create socket." << endl; exit(EXIT_FAILURE); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(443); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(serverSock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { cout << "Failed to bind socket." << endl; exit(EXIT_FAILURE); } listen(serverSock, 5); while (true) { cout << "Wait client connecting..." << endl; struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int clientSock = accept(serverSock, (struct sockaddr*)&client_addr, &addr_len); SSL *pSsl = SSL_new(ctx); // 關(guān)聯(lián)已有的Socket句柄與SSL結(jié)構(gòu)體 SSL_set_fd(pSsl, clientSock); // 執(zhí)行握手 if (SSL_accept(pSsl) <= 0) { ERR_print_errors_fp(stdout); } else { char pBuff[1024] = { 0 }; int nBytesReceived = SSL_read(pSsl, pBuff, sizeof(pBuff) - 1); if (nBytesReceived > 0) { cout << "Received msg: " << pBuff << endl; // 向客戶端回傳消息 string strRsp = "Hello from Hope Wisdom"; SSL_write(pSsl, strRsp.c_str(), strRsp.size()); } } SSL_free(pSsl); close(clientSock); } close(serverSock); SSL_CTX_free(ctx); return 0; }
雙向認(rèn)證
通常情況下,對(duì)于標(biāo)準(zhǔn)的HTTPS連接(比如:瀏覽器訪問(wèn)一個(gè)安全網(wǎng)站),客戶端并不需要提供自己的證書或私鑰。服務(wù)器會(huì)向客戶端發(fā)送其證書,客戶端使用預(yù)置的信任根證書來(lái)驗(yàn)證服務(wù)器的身份。
然而,在某些對(duì)安全性要求更高的場(chǎng)景下,比如:企業(yè)內(nèi)部網(wǎng)絡(luò)、金融交易系統(tǒng)等,服務(wù)器可能會(huì)要求客戶端也提供證書以證明自己的身份。這種機(jī)制,被稱為雙向認(rèn)證。當(dāng)客戶端也需要進(jìn)行身份驗(yàn)證時(shí),除了要驗(yàn)證服務(wù)器提供的證書外,還需要準(zhǔn)備好自己的證書和私鑰,并將它們配置到SSL/TLS上下文中。
下面的示例代碼,演示了客戶端程序如何設(shè)置證書與私鑰來(lái)完成雙向認(rèn)證。
#include <iostream> #include <openssl/ssl.h> #include <openssl/err.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { if (OPENSSL_init_crypto(0, nullptr) != 1) { cout << "Failed to initialize OpenSSL crypto library." << endl; exit(EXIT_FAILURE); } if (OPENSSL_init_ssl(0, nullptr) != 1) { cout << "Failed to initialize OpenSSL SSL library." << endl; exit(EXIT_FAILURE); } // 使用最新的TLS版本 const SSL_METHOD *method = TLS_client_method(); SSL_CTX *ctx = SSL_CTX_new(method); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 配置客戶端證書 if (SSL_CTX_use_certificate_file(ctx, "client.crt", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 配置客戶端私鑰 if (SSL_CTX_use_PrivateKey_file(ctx, "client.key", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 創(chuàng)建socket int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { cout << "Failed to create socket." << endl; return -1; } // 設(shè)置服務(wù)器地址 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(443); // 假設(shè)服務(wù)器IP為本地回環(huán)地址 inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 連接到服務(wù)器 if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { cout << "Failed to connect" << endl; close(sock); return -1; } SSL *pSsl = SSL_new(ctx); // 關(guān)聯(lián)已有的Socket句柄與SSL結(jié)構(gòu)體 SSL_set_fd(pSsl, sock); // 執(zhí)行握手 if (SSL_connect(pSsl) <= 0) { ERR_print_errors_fp(stdout); goto end; } // 發(fā)送消息給服務(wù)器 const char* pMsg = "Hi, Hope Wisdom"; SSL_write(pSsl, pMsg, strlen(pMsg)); // 接收響應(yīng) char pBuff[1024] = {0}; int nBytesReceived = SSL_read(pSsl, pBuff, sizeof(pBuff) - 1); if (nBytesReceived > 0) { cout << "Received msg: " << pBuff << endl; } else { cout << "Failed to receive msg" << endl; } end: SSL_free(pSsl); close(sock); SSL_CTX_free(ctx); return 0; }
在上面的示例代碼中,client.crt和client.key表示客戶端的證書和私鑰文件。這些文件必須預(yù)先生成好,并放置于程序可以訪問(wèn)的位置。另外,還需要確保服務(wù)端已正確配置允許客戶端認(rèn)證,并且加載了用于驗(yàn)證客戶端證書的CA證書。
至于服務(wù)端如何配置,可參考下面的示例代碼。
// 設(shè)置驗(yàn)證模式為需要客戶端證書 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); // 加載CA證書 if (SSL_CTX_load_verify_locations(ctx, "ca.crt", nullptr) != 1) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); }
在上面的示例代碼中,SSL_CTX_set_verify設(shè)置了客戶端驗(yàn)證策略。我們?cè)O(shè)置了標(biāo)志位SSL_VERIFY_PEERh和SSL_VERIFY_FAIL_IF_NO_PEER_CERT,這意味著服務(wù)端會(huì)強(qiáng)制要求客戶端提供證書。如果客戶端沒(méi)有提供證書,則握手失敗。SSL_CTX_load_verify_locations函數(shù)指定了一個(gè)或多個(gè)CA證書文件的位置,這些證書用于驗(yàn)證客戶端提供的證書是否有效。如果沒(méi)有找到合適的CA證書來(lái)驗(yàn)證客戶端證書,握手也會(huì)失敗。
總結(jié)
到此這篇關(guān)于C++中如何實(shí)現(xiàn)SSL/TLS加密通信的文章就介紹到這了,更多相關(guān)C++中SSL/TLS加密通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)單計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05C語(yǔ)言實(shí)現(xiàn)制作通訊錄(新手推薦)
本文推薦給C語(yǔ)言學(xué)習(xí)到結(jié)構(gòu)體的新手們,供其練習(xí)。這篇文章主要是利用C語(yǔ)言制作一個(gè)簡(jiǎn)單的通訊錄功能,感興趣的小伙伴可以跟隨小編一起了解一下2022-09-09C語(yǔ)言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表
這篇文章主要為大家詳細(xì)介紹了c語(yǔ)言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C++中vector和數(shù)組之間的轉(zhuǎn)換及其效率問(wèn)題詳解
c++?vector轉(zhuǎn)數(shù)組是一種將vector容器的元素轉(zhuǎn)換為數(shù)組的方法,主要能幫助提高程序的性能和效率,下面這篇文章主要給大家介紹了關(guān)于C++中vector和數(shù)組之間的轉(zhuǎn)換及其效率問(wèn)題的相關(guān)資料,需要的朋友可以參考下2023-03-03C++使用一棵紅黑樹同時(shí)封裝出map和set實(shí)例代碼
紅黑樹(Red?Black?Tre)是一種自平衡二叉查找樹,是在計(jì)算機(jī)科學(xué)中用到的一種數(shù)據(jù)結(jié)構(gòu),典型的用途是實(shí)現(xiàn)關(guān)聯(lián)數(shù)組,下面這篇文章主要給大家介紹了關(guān)于C++使用一棵紅黑樹同時(shí)封裝出map和set的相關(guān)資料,需要的朋友可以參考下2023-04-04淺析C++中strlen函數(shù)的使用與模擬實(shí)現(xiàn)strlen的方法
這篇文章主要介紹了strlen函數(shù)的使用與模擬實(shí)現(xiàn)strlen的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03C語(yǔ)言rand和srand函數(shù)使用方法介紹
rand()函數(shù)用來(lái)產(chǎn)生隨機(jī)數(shù),但是,rand()的內(nèi)部實(shí)現(xiàn)是用線性同余法實(shí)現(xiàn)的,是偽隨機(jī)數(shù),由于周期較長(zhǎng),因此在一定范圍內(nèi)可以看成是隨機(jī)的。srand()用來(lái)設(shè)置rand()產(chǎn)生隨機(jī)數(shù)時(shí)的隨機(jī)數(shù)種子。參數(shù)seed是整數(shù),通??梢岳胻ime(0)或geypid(0)的返回值作為seed2023-02-02