找回密码
 立即注册
查看: 9|回复: 0

C++网络编程之SSL/TLS加密通信

[复制链接]

30

主题

2

回帖

164

积分

管理员

积分
164
发表于 2025-3-24 08:52:49 | 显示全部楼层 |阅读模式
在互联网时代,数据的安全性变得尤为重要。随着网络安全威胁的不断增加,确保信息传输过程中的机密性、完整性和可用性成为了开发者必须考虑的关键因素。在C++网络编程中,使用SSL/TLS加密通信是一种常见的做法。它允许客户端和服务器之间通过互联网安全地交换信息,从而为网络通信提供隐私性和数据完整性。

SSL,英文全称为Secure Sockets Layer,最初由Netscape公司在1990年代开发,用于保护Web浏览器与服务器间的通信。TLS,英文全称为Transport Layer Security,是IETF标准化后的版本,可以看作是SSL的继承者。尽管名字不同,但两者提供的功能非常相似,通常会把它们统称为“SSL/TLS”。
基本概念
SSL/TLS:一种用于在两个通信应用程序之间提供保密性和数据完整性的协议。

证书:一种数字文档,包含了一个实体的信息及其公钥。它由一个可信赖的第三方机构(CA,即Certificate Authority)签发,以证明该实体的身份。

公钥/私钥: 每个参与者都有一对密钥,主要用于非对称加密算法。公钥公开给所有人,用于加密或验证签名。私钥则保密保存,仅用于解密或创建签名。这保证了即使数据被拦截,没有私钥也无法读取其内容。非对称加密算法的安全性在于:计算上难以从公钥推导出私钥。常见的非对称加密算法包括:RSA、ECC等。

会话密钥:一旦客户端与服务器建立了信任关系,就会生成一个临时的对称密钥,来进行后续的数据加密和解密工作。对称加密算法因为其加密解密速度快,在大量数据传输中非常有用。常用的对称加密算法有:AES、DES等。

公钥/私钥与会话密钥之间的主要关系在于:安全地建立对称加密会话。具体来说,有如下两个主要步骤。

1、使用非对称加密来安全地交换会话密钥。比如:小王可以使用小张的公钥加密一个会话密钥,并将它发送给小张;只有拥有相应私钥的小张,才能解密该会话密钥。

2、一旦会话密钥被安全地交换,小王和小张就可以使用这个会话密钥,并通过对称加密算法来加密实际传输的数据。


API接口
OpenSSL是一个开源软件包,提供了丰富的函数用于实现SSL/TLS加密通信,一些常用的API如下。

SSL_CTX_new:创建一个新的SSL上下文。

SSL_CTX_use_certificate_file:用于设置证书文件路径。

SSL_CTX_use_PrivateKey_file:用于设置私钥文件路径。

SSL_new:根据给定的SSL上下文创建一个新的SSL结构体实例。

SSL_set_fd:将已有的socket描述符绑定至SSL对象上。

SSL_connect:客户端调用此函数开始SSL握手过程。

SSL_accept:服务端调用此函数开始SSL握手过程。

SSL_read:用于读取加密后的数据流。

SSL_write:用于写入加密后的数据流。

SSL_shutdown:发起关闭SSL连接的过程。

使用OpenSSL库进行SSL/TLS加密通信的主要步骤如下。

1、初始化OpenSSL库。通常情况下,我们需要调用SSL_library_init等函数来初始化环境。

2、创建SSL上下文。使用SSL_CTX_new创建一个新的SSL_CTX对象,并配置相关的选项。比如:选择使用的协议版本为TLSv1.2、TLSv1.3等。

3、加载证书文件。如果作为服务端运行,则需要加载自己的证书及私钥。如果是客户端,则可能需要指定CA证书列表,以验证服务器的身份。

4、建立普通套接字连接。与普通的TCP/IP编程一样,先完成两台机器之间的基本连接。

5、将普通套接字转换成SSL套接字。通过SSL_set_fd函数,关联已有的Socket句柄与SSL结构体。

6、握手。双方交换各自的信息,并协商加密算法、生成共享的密钥等。这一过程,通过SSL_connect或SSL_accept函数完成。

7、数据传输。使用SSL_read和SSL_write代替标准的read和write操作,以确保所有发送和接收的数据都是加密的。

8、关闭连接。正常情况下,应先调用SSL_shutdown()进行优雅断开,之后再关闭底层socket。

在C++中如何进行SSL/TLS加密通信,可参考下面服务端的代码。
  1. #include <iostream>
  2. #include <openssl/ssl.h>
  3. #include <openssl/err.h>
  4. #include <string>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <unistd.h>
  8. #include <cstdlib>

  9. using namespace std;

  10. int main()
  11. {
  12.     if (OPENSSL_init_crypto(0, nullptr) != 1)
  13.     {
  14.         cout << "Failed to initialize OpenSSL crypto library." << endl;
  15.         exit(EXIT_FAILURE);
  16.     }

  17.     if (OPENSSL_init_ssl(0, nullptr) != 1)
  18.     {
  19.         cout << "Failed to initialize OpenSSL SSL library." << endl;
  20.         exit(EXIT_FAILURE);
  21.     }

  22.     // 使用最新的TLS版本
  23.     const SSL_METHOD *method = TLS_server_method();
  24.     SSL_CTX *ctx = SSL_CTX_new(method);
  25.     if (ctx == nullptr)
  26.     {
  27.         ERR_print_errors_fp(stdout);
  28.         exit(EXIT_FAILURE);
  29.     }

  30.     // 配置服务器证书
  31.     if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0)
  32.     {
  33.         ERR_print_errors_fp(stdout);
  34.         exit(EXIT_FAILURE);
  35.     }

  36.     // 配置服务器私钥
  37.     if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0)
  38.     {
  39.         ERR_print_errors_fp(stdout);
  40.         exit(EXIT_FAILURE);
  41.     }

  42.     // 检查私钥是否匹配证书
  43.     if (!SSL_CTX_check_private_key(ctx))
  44.     {
  45.         cout << "Private key does not match the certificate public key." << endl;
  46.         exit(EXIT_FAILURE);
  47.     }

  48.     // 开启监听
  49.     int serverSock = socket(AF_INET, SOCK_STREAM, 0);
  50.     if (serverSock < 0)
  51.     {
  52.         cout << "Failed to create socket." << endl;
  53.         exit(EXIT_FAILURE);
  54.     }

  55.     struct sockaddr_in server_addr;
  56.     memset(&server_addr, 0, sizeof(server_addr));
  57.     server_addr.sin_family = AF_INET;
  58.     server_addr.sin_port = htons(443);
  59.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  60.     if (bind(serverSock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
  61.     {
  62.         cout << "Failed to bind socket." << endl;
  63.         exit(EXIT_FAILURE);
  64.     }

  65.     listen(serverSock, 5);
  66.     while (true)
  67.     {
  68.         cout << "Wait client connecting..." << endl;

  69.         struct sockaddr_in client_addr;
  70.         socklen_t addr_len = sizeof(client_addr);
  71.         int clientSock = accept(serverSock, (struct sockaddr*)&client_addr, &addr_len);

  72.         SSL *pSsl = SSL_new(ctx);
  73.         // 关联已有的Socket句柄与SSL结构体
  74.         SSL_set_fd(pSsl, clientSock);

  75.         // 执行握手
  76.         if (SSL_accept(pSsl) <= 0)
  77.         {
  78.             ERR_print_errors_fp(stdout);
  79.         }
  80.         else
  81.         {
  82.             char pBuff[1024] = { 0 };
  83.             int nBytesReceived = SSL_read(pSsl, pBuff, sizeof(pBuff) - 1);
  84.             if (nBytesReceived > 0)
  85.             {
  86.                 cout << "Received msg: " << pBuff << endl;

  87.                 // 向客户端回传消息
  88.                 string strRsp = "Hello from Hope Wisdom";
  89.                 SSL_write(pSsl, strRsp.c_str(), strRsp.size());
  90.             }
  91.         }

  92.         SSL_free(pSsl);
  93.         close(clientSock);
  94.     }

  95.     close(serverSock);
  96.     SSL_CTX_free(ctx);
  97.     return 0;
  98. }
复制代码
双向认证
通常情况下,对于标准的HTTPS连接(比如:浏览器访问一个安全网站),客户端并不需要提供自己的证书或私钥。服务器会向客户端发送其证书,客户端使用预置的信任根证书来验证服务器的身份。

然而,在某些对安全性要求更高的场景下,比如:企业内部网络、金融交易系统等,服务器可能会要求客户端也提供证书以证明自己的身份。这种机制,被称为双向认证。当客户端也需要进行身份验证时,除了要验证服务器提供的证书外,还需要准备好自己的证书和私钥,并将它们配置到SSL/TLS上下文中。

下面的示例代码,演示了客户端程序如何设置证书与私钥来完成双向认证。

  1. #include <iostream>
  2. #include <openssl/ssl.h>
  3. #include <openssl/err.h>
  4. #include <string.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <unistd.h>

  8. using namespace std;

  9. int main()
  10. {
  11.     if (OPENSSL_init_crypto(0, nullptr) != 1)
  12.     {
  13.         cout << "Failed to initialize OpenSSL crypto library." << endl;
  14.         exit(EXIT_FAILURE);
  15.     }

  16.     if (OPENSSL_init_ssl(0, nullptr) != 1)
  17.     {
  18.         cout << "Failed to initialize OpenSSL SSL library." << endl;
  19.         exit(EXIT_FAILURE);
  20.     }

  21.     // 使用最新的TLS版本
  22.     const SSL_METHOD *method = TLS_client_method();
  23.     SSL_CTX *ctx = SSL_CTX_new(method);
  24.     if (ctx == NULL)
  25.     {
  26.         ERR_print_errors_fp(stdout);
  27.         exit(EXIT_FAILURE);
  28.     }

  29.     // 配置客户端证书
  30.     if (SSL_CTX_use_certificate_file(ctx, "client.crt", SSL_FILETYPE_PEM) <= 0)
  31.     {
  32.         ERR_print_errors_fp(stdout);
  33.         exit(EXIT_FAILURE);
  34.     }

  35.     // 配置客户端私钥
  36.     if (SSL_CTX_use_PrivateKey_file(ctx, "client.key", SSL_FILETYPE_PEM) <= 0)
  37.     {
  38.         ERR_print_errors_fp(stdout);
  39.         exit(EXIT_FAILURE);
  40.     }

  41.     // 创建socket
  42.     int sock = socket(AF_INET, SOCK_STREAM, 0);
  43.     if (sock < 0)
  44.     {
  45.         cout << "Failed to create socket." << endl;
  46.         return -1;
  47.     }

  48.     // 设置服务器地址
  49.     struct sockaddr_in server_addr;
  50.     memset(&server_addr, 0, sizeof(server_addr));
  51.     server_addr.sin_family = AF_INET;
  52.     server_addr.sin_port = htons(443);
  53.     // 假设服务器IP为本地回环地址
  54.     inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

  55.     // 连接到服务器
  56.     if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
  57.     {
  58.         cout << "Failed to connect" << endl;
  59.         close(sock);
  60.         return -1;
  61.     }

  62.     SSL *pSsl = SSL_new(ctx);
  63.     // 关联已有的Socket句柄与SSL结构体
  64.     SSL_set_fd(pSsl, sock);

  65.     // 执行握手
  66.     if (SSL_connect(pSsl) <= 0)
  67.     {
  68.         ERR_print_errors_fp(stdout);
  69.         goto end;
  70.     }

  71.     // 发送消息给服务器
  72.     const char* pMsg = "Hi, Hope Wisdom";
  73.     SSL_write(pSsl, pMsg, strlen(pMsg));

  74.     // 接收响应
  75.     char pBuff[1024] = {0};
  76.     int nBytesReceived = SSL_read(pSsl, pBuff, sizeof(pBuff) - 1);
  77.     if (nBytesReceived > 0)
  78.     {
  79.         cout << "Received msg: " << pBuff << endl;
  80.     }
  81.     else
  82.     {
  83.         cout << "Failed to receive msg" << endl;
  84.     }

  85. end:
  86.     SSL_free(pSsl);
  87.     close(sock);
  88.     SSL_CTX_free(ctx);
  89.     return 0;
  90. }
复制代码
在上面的示例代码中,client.crt和client.key表示客户端的证书和私钥文件。这些文件必须预先生成好,并放置于程序可以访问的位置。另外,还需要确保服务端已正确配置允许客户端认证,并且加载了用于验证客户端证书的CA证书。

  1. // 设置验证模式为需要客户端证书
  2. SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);

  3. // 加载CA证书
  4. if (SSL_CTX_load_verify_locations(ctx, "ca.crt", nullptr) != 1)
  5. {
  6.     ERR_print_errors_fp(stdout);
  7.     exit(EXIT_FAILURE);
  8. }
复制代码
在上面的示例代码中,SSL_CTX_set_verify设置了客户端验证策略。我们设置了标志位SSL_VERIFY_PEERh和SSL_VERIFY_FAIL_IF_NO_PEER_CERT,这意味着服务端会强制要求客户端提供证书。如果客户端没有提供证书,则握手失败。SSL_CTX_load_verify_locations函数指定了一个或多个CA证书文件的位置,这些证书用于验证客户端提供的证书是否有效。如果没有找到合适的CA证书来验证客户端证书,握手也会失败。
-----------------------------------
©著作权归作者所有:来自51CTO博客作者希望睿智的技术小屋的原创作品
C++网络编程之SSL/TLS加密通信
https://blog.51cto.com/u_16794707/13056438

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|软件开发编程门户 ( 陇ICP备2024013992号-1|甘公网安备62090002000130号 )

GMT+8, 2025-4-6 22:52 , Processed in 0.181654 second(s), 20 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表