C++中如何實現(xiàn)SSL/TLS加密通信
概述
在互聯(lián)網(wǎng)時代,數(shù)據(jù)的安全性變得尤為重要。隨著網(wǎng)絡安全威脅的不斷增加,確保信息傳輸過程中的機密性、完整性和可用性成為了開發(fā)者必須考慮的關鍵因素。在C++網(wǎng)絡編程中,使用SSL/TLS加密通信是一種常見的做法。它允許客戶端和服務器之間通過互聯(lián)網(wǎng)安全地交換信息,從而為網(wǎng)絡通信提供隱私性和數(shù)據(jù)完整性。
SSL,英文全稱為Secure Sockets Layer,最初由Netscape公司在1990年代開發(fā),用于保護Web瀏覽器與服務器間的通信。TLS,英文全稱為Transport Layer Security,是IETF標準化后的版本,可以看作是SSL的繼承者。盡管名字不同,但兩者提供的功能非常相似,通常會把它們統(tǒng)稱為“SSL/TLS”。
基本概念
SSL/TLS:一種用于在兩個通信應用程序之間提供保密性和數(shù)據(jù)完整性的協(xié)議。
證書:一種數(shù)字文檔,包含了一個實體的信息及其公鑰。它由一個可信賴的第三方機構(CA,即Certificate Authority)簽發(fā),以證明該實體的身份。
公鑰/私鑰: 每個參與者都有一對密鑰,主要用于非對稱加密算法。公鑰公開給所有人,用于加密或驗證簽名。私鑰則保密保存,僅用于解密或創(chuàng)建簽名。這保證了即使數(shù)據(jù)被攔截,沒有私鑰也無法讀取其內(nèi)容。非對稱加密算法的安全性在于:計算上難以從公鑰推導出私鑰。常見的非對稱加密算法包括:RSA、ECC等。
會話密鑰:一旦客戶端與服務器建立了信任關系,就會生成一個臨時的對稱密鑰,來進行后續(xù)的數(shù)據(jù)加密和解密工作。對稱加密算法因為其加密解密速度快,在大量數(shù)據(jù)傳輸中非常有用。常用的對稱加密算法有:AES、DES等。
公鑰/私鑰與會話密鑰之間的主要關系在于:安全地建立對稱加密會話。具體來說,有如下兩個主要步驟。
1、使用非對稱加密來安全地交換會話密鑰。比如:小王可以使用小張的公鑰加密一個會話密鑰,并將它發(fā)送給小張;只有擁有相應私鑰的小張,才能解密該會話密鑰。
2、一旦會話密鑰被安全地交換,小王和小張就可以使用這個會話密鑰,并通過對稱加密算法來加密實際傳輸?shù)臄?shù)據(jù)。
API接口
OpenSSL是一個開源軟件包,提供了豐富的函數(shù)用于實現(xiàn)SSL/TLS加密通信,一些常用的API如下。
SSL_CTX_new:創(chuàng)建一個新的SSL上下文。
SSL_CTX_use_certificate_file:用于設置證書文件路徑。
SSL_CTX_use_PrivateKey_file:用于設置私鑰文件路徑。
SSL_new:根據(jù)給定的SSL上下文創(chuàng)建一個新的SSL結構體實例。
SSL_set_fd:將已有的socket描述符綁定至SSL對象上。
SSL_connect:客戶端調用此函數(shù)開始SSL握手過程。
SSL_accept:服務端調用此函數(shù)開始SSL握手過程。
SSL_read:用于讀取加密后的數(shù)據(jù)流。
SSL_write:用于寫入加密后的數(shù)據(jù)流。
SSL_shutdown:發(fā)起關閉SSL連接的過程。
使用OpenSSL庫進行SSL/TLS加密通信的主要步驟如下。
1、初始化OpenSSL庫。通常情況下,我們需要調用SSL_library_init等函數(shù)來初始化環(huán)境。
2、創(chuàng)建SSL上下文。使用SSL_CTX_new創(chuàng)建一個新的SSL_CTX對象,并配置相關的選項。比如:選擇使用的協(xié)議版本為TLSv1.2、TLSv1.3等。
3、加載證書文件。如果作為服務端運行,則需要加載自己的證書及私鑰。如果是客戶端,則可能需要指定CA證書列表,以驗證服務器的身份。
4、建立普通套接字連接。與普通的TCP/IP編程一樣,先完成兩臺機器之間的基本連接。
5、將普通套接字轉換成SSL套接字。通過SSL_set_fd函數(shù),關聯(lián)已有的Socket句柄與SSL結構體。
6、握手。雙方交換各自的信息,并協(xié)商加密算法、生成共享的密鑰等。這一過程,通過SSL_connect或SSL_accept函數(shù)完成。
7、數(shù)據(jù)傳輸。使用SSL_read和SSL_write代替標準的read和write操作,以確保所有發(fā)送和接收的數(shù)據(jù)都是加密的。
8、關閉連接。正常情況下,應先調用SSL_shutdown()進行優(yōu)雅斷開,之后再關閉底層socket。
在C++中如何進行SSL/TLS加密通信,可參考下面服務端的代碼。
#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); } // 配置服務器證書 if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(EXIT_FAILURE); } // 配置服務器私鑰 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); // 關聯(lián)已有的Socket句柄與SSL結構體 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; }
雙向認證
通常情況下,對于標準的HTTPS連接(比如:瀏覽器訪問一個安全網(wǎng)站),客戶端并不需要提供自己的證書或私鑰。服務器會向客戶端發(fā)送其證書,客戶端使用預置的信任根證書來驗證服務器的身份。
然而,在某些對安全性要求更高的場景下,比如:企業(yè)內(nèi)部網(wǎng)絡、金融交易系統(tǒng)等,服務器可能會要求客戶端也提供證書以證明自己的身份。這種機制,被稱為雙向認證。當客戶端也需要進行身份驗證時,除了要驗證服務器提供的證書外,還需要準備好自己的證書和私鑰,并將它們配置到SSL/TLS上下文中。
下面的示例代碼,演示了客戶端程序如何設置證書與私鑰來完成雙向認證。
#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; } // 設置服務器地址 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(443); // 假設服務器IP為本地回環(huán)地址 inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 連接到服務器 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); // 關聯(lián)已有的Socket句柄與SSL結構體 SSL_set_fd(pSsl, sock); // 執(zhí)行握手 if (SSL_connect(pSsl) <= 0) { ERR_print_errors_fp(stdout); goto end; } // 發(fā)送消息給服務器 const char* pMsg = "Hi, Hope Wisdom"; SSL_write(pSsl, pMsg, strlen(pMsg)); // 接收響應 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表示客戶端的證書和私鑰文件。這些文件必須預先生成好,并放置于程序可以訪問的位置。另外,還需要確保服務端已正確配置允許客戶端認證,并且加載了用于驗證客戶端證書的CA證書。
至于服務端如何配置,可參考下面的示例代碼。
// 設置驗證模式為需要客戶端證書 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設置了客戶端驗證策略。我們設置了標志位SSL_VERIFY_PEERh和SSL_VERIFY_FAIL_IF_NO_PEER_CERT,這意味著服務端會強制要求客戶端提供證書。如果客戶端沒有提供證書,則握手失敗。SSL_CTX_load_verify_locations函數(shù)指定了一個或多個CA證書文件的位置,這些證書用于驗證客戶端提供的證書是否有效。如果沒有找到合適的CA證書來驗證客戶端證書,握手也會失敗。
總結
到此這篇關于C++中如何實現(xiàn)SSL/TLS加密通信的文章就介紹到這了,更多相關C++中SSL/TLS加密通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言實現(xiàn)通用數(shù)據(jù)結構之通用鏈表
這篇文章主要為大家詳細介紹了c語言實現(xiàn)通用數(shù)據(jù)結構之通用鏈表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11C++中vector和數(shù)組之間的轉換及其效率問題詳解
c++?vector轉數(shù)組是一種將vector容器的元素轉換為數(shù)組的方法,主要能幫助提高程序的性能和效率,下面這篇文章主要給大家介紹了關于C++中vector和數(shù)組之間的轉換及其效率問題的相關資料,需要的朋友可以參考下2023-03-03淺析C++中strlen函數(shù)的使用與模擬實現(xiàn)strlen的方法
這篇文章主要介紹了strlen函數(shù)的使用與模擬實現(xiàn)strlen的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03