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

概述

        在互联网时代,数据的安全性变得尤为重要。随着网络安全威胁的不断增加,确保信息传输过程中的机密性、完整性和可用性成为了开发者必须考虑的关键因素。在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加密通信,可参考下面服务端的代码。

#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);
    }

    // 开启监听
    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);
        // 关联已有的Socket句柄与SSL结构体
        SSL_set_fd(pSsl, clientSock);

        // 执行握手
        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连接(比如:浏览器访问一个安全网站),客户端并不需要提供自己的证书或私钥。服务器会向客户端发送其证书,客户端使用预置的信任根证书来验证服务器的身份。

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

    // 创建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为本地回环地址
    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);
    // 关联已有的Socket句柄与SSL结构体
    SSL_set_fd(pSsl, sock);

    // 执行握手
    if (SSL_connect(pSsl) <= 0)
    {
        ERR_print_errors_fp(stdout);
        goto end;
    }

    // 发送消息给服务器
    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函数指定了一个或多个CA证书文件的位置,这些证书用于验证客户端提供的证书是否有效。如果没有找到合适的CA证书来验证客户端证书,握手也会失败。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/918062.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring-事务学习

spring事务 1. 什么是事务? 事务其实是一个并发控制单位&#xff0c;是用户定义的一个操作序列&#xff0c;这些操作要么全部完成&#xff0c;要不全部不完成&#xff0c;是一个不可分割的工作单位。事务有 ACID 四个特性&#xff0c;即&#xff1a; 原子性&#xff08;Atom…

RHCE的学习(21)

第三章 Shell条件测试 用途 为了能够正确处理Shell程序运行过程中遇到的各种情况&#xff0c;Linux Shell提供了一组测试运算符。 通过这些运算符&#xff0c;Shell程序能够判断某种或者几个条件是否成立。 条件测试在各种流程控制语句&#xff0c;例如判断语句和循环语句中…

用pyspark把kafka主题数据经过etl导入另一个主题中的有关报错

首先看一下我们的示例代码 import os from pyspark.sql import SparkSession import pyspark.sql.functions as F """ ------------------------------------------Description : TODO&#xff1a;SourceFile : etl_stream_kafkaAuthor : zxxDate : 2024/11/…

单片机_day3_GPIO

目录 1. 灯如何才能亮 1.1原理图 1.2 二极管 1.3 换了一个灯和原理图 ​编辑 1.4 三极管 1.4.1 NPN型三极管 1.4.2 PNP型三极管 2. 基本概念 3. 输入 3.1 浮空输入 3.2 上拉输入 3.3 下拉输入 3.4 模拟输入 4. 输出 4.1 推挽输出 4.2 开漏输出 如何让开漏输出…

基于视觉智能的时间序列基础模型

GitHub链接&#xff1a;ViTime: A Visual Intelligence-Based Foundation Model for Time Series Forecasting 论文链接&#xff1a;https://github.com/IkeYang/ViTime 前言 作者是来自西安理工大学&#xff0c;西北工业大学&#xff0c;以色列理工大学以及香港城市大学的研…

java项目-jenkins任务的创建和执行

参考内容: jenkins的安装部署以及全局配置 1.编译任务的general 2.源码管理 3.构建里编译打包然后copy复制jar包到运行服务器的路径 clean install -DskipTests -Pdev 中的-Pdev这个参数用于激活 Maven 项目中的特定构建配置&#xff08;Profile&#xff09; 在 pom.xml 文件…

Qt按钮类-->day09

按钮基类 QAbstractButton 标题与图标 // 参数text的内容显示到按钮上 void QAbstractButton::setText(const QString &text); // 得到按钮上显示的文本内容, 函数的返回就是 QString QAbstractButton::text() const;// 得到按钮设置的图标 QIcon icon() const; // 给按钮…

论文6—《基于YOLOv5s的深度学习在自然场景苹果花朵检测中的应用》文献阅读分析报告

论文报告&#xff1a;基于YOLOv5s的深度学习在自然场景苹果花朵检测中的应用 基于YOLOv5s的深度学习在自然场景苹果花朵检测中的应用 摘要国内外研究现状1. 疏花技术研究2. 目标检测算法研究 研究目的研究问题使用的研究方法试验研究结果文献结论创新点和对现有研究的贡献1. Y…

「人眼视觉不再是视频消费的唯一形式」丨智能编解码和 AI 视频生成专场回顾@RTE2024

你是否想过&#xff0c;未来你看到的电影预告片、广告&#xff0c;甚至新闻报道&#xff0c;都可能完全由 AI 生成&#xff1f; 在人工智能迅猛发展的今天&#xff0c;视频技术正经历着一场前所未有的变革。从智能编解码到虚拟数字人&#xff0c;再到 AI 驱动的视频生成&#…

计算机毕业设计Python美食推荐系统 美团爬虫 美食可视化 机器学习 深度学习 混合神经网络推荐算法 Hadoop Spark 人工智能 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

GPU分布式通信技术-PCle、NVLink、NVSwitch深度解析

GPU分布式通信技术-PCle、NVLink、NVSwitch 大模型时代已到来&#xff0c;成为AI核心驱动力。然而&#xff0c;训练大模型却面临巨大挑战&#xff1a;庞大的GPU资源需求和漫长的学习过程。 要实现跨多个 GPU 的模型训练&#xff0c;需要使用分布式通信和 NVLink。此外&#xf…

MySQL:联合查询(2)

首先写一个三个表的联合查询 查询所有同学的每门课成绩&#xff0c;及同学的个人信息 1.我们首先要确定使用哪些表 学生表&#xff0c;课程表&#xff0c;成绩表 2.取笛卡尔积 select * from score,student,course; 3. 确定表与表之间的联合条件 select * from score,stud…

【leetcode】704. 二分查找

注意一般mid left (right-left)/2; 不要用mid (right - left)/2 中间值的计算需要考虑到整型溢出的问题。 如果使用 mid (right - left) / 2 的方式计算中间值&#xff0c;那么在 right 和 left 的值接近极限值的情况下&#xff0c;可能会导致计算出的中间值发生整型溢出&…

RHCE的练习(12)

写一个脚本&#xff0c;完成以下要求&#xff1a; 给定一个用户&#xff1a; 如果其UID为0&#xff0c;就显示此为管理员&#xff1b;否则&#xff0c;就显示其为普通用户&#xff1b; #!/bin/bash ​ # 使用read命令获取用户名 read -p "请输入用户名: " username ​…

WPF-控件的属性值的类型转化

控件的属性值需要转成int、double进行运算的&#xff0c;可以使用一下方法 页面代码 <StackPanel Margin"4,0,0,0" Style"{StaticResource Form-StackPanel}"> <Label Content"替换后材料增加金额&#xff…

【从零开始的LeetCode-算法】3270. 求出数字答案

给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。答案 key 的第 i 个数位&#xff08;1 < …

iMetaOmics | 刘永鑫/陈同-用于食物微生物组成和时间序列研究的微生物组数据库FoodMicroDB...

点击蓝字 关注我们 FoodMicroDB&#xff1a;用于食物微生物组成和时间序列研究的微生物组数据库 iMeta主页&#xff1a;http://www.imeta.science 研究论文 ● 原文链接DOI: https://doi.org/10.1002/imo2.40 ● 2024年11月1日&#xff0c;中国农业科学院深圳农业基因组研究所刘…

视觉slam十四讲 ch8 光流法和直接法

之前的都是单层光流 转载至Blibli 视觉SLAM十四讲_7视觉里程计1_计算相机运动_哔哩哔哩_bilibili

QSS 设置bug

问题描述&#xff1a; 在QWidget上add 一个QLabel&#xff0c;但是死活不生效 原因&#xff1a; c 主程序如下&#xff1a; QWidget* LOGO new QWidget(logo_wnd);LOGO->setFixedSize(logo_width, 41);LOGO->setObjectName("TittltLogo");QVBoxLayout* tit…

Linux运维篇-iscsi存储搭建

目录 概念实验介绍环境准备存储端软件安装使用targetcli来管理iSCSI共享存储 客户端软件安装连接存储 概念 iSCSI是一种在Internet协议上&#xff0c;特别是以太网上进行数据块传输的标准&#xff0c;它是一种基于IP Storage理论的存储技术&#xff0c;该技术是将存储行业广泛…