基于openssl v3搭建ssl安全加固的c++ tcpserver

news2024/9/25 12:01:41

1 概述

tcp server和tcp client同时使用openssl库,可对通信双方流通的字节序列进行加解密,保障通信的安全。本文以c++编写的tcp server和tcp client为例子,openssl的版本为v3。

2 安装openssl v3

2.1 安装 perl-IPC-Cmd

openssl项目中的config脚本需要用到perl-IPC-Cmd工具。

yum -y install perl-IPC-Cmd

2.2 安装openssl v3

安装的结果放在目录/opt/openssl中。

mkdir -p /opt
cd /opt
wget --no-check-certificate https://www.openssl.org/source/openssl-3.1.0.tar.gz
tar xf openssl-3.1.0.tar.gz
cd openssl-3.1.0
./config --prefix=/opt/openssl
make -j8
make install

安装完毕后,结果如下图所示:
在这里插入图片描述


3 生成SSL私钥和证书

3.1 创建自签CA:

以下将生成一个名称为ca.key的key文件和一个名称为ca.crt的证书文件,CA的common name叫EXAMPLE Cert Authority。

openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt \
-days 3650 \
-nodes -subj '/CN=EXAMPLE Cert Authority'

3.2 生成key文件和csr文件

key文件名称为server.key,csr文件名称为server.csr,common name为apigateway.example.com。

openssl req -new -newkey rsa:4096 -keyout server.key -out server.csr -nodes \
-subj '/CN=apigateway.example.com'

3.2 编写csr扩展文件extfile

SAN就在subjectAltName字段中指定,可以写多个域名和IP,例如域名为apigateway,IP为192.168.16.128。

cat > extfile.cnf << EOF
[ v3_req ]
# Extensions to add to a certificate request
extendedKeyUsage = serverAuth,clientAuth
subjectAltName = DNS:apigateway.example.com,DNS:localhost,IP:192.168.16.128,IP:127.0.0.1
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
EOF

3.3 生成证书文件

依据key文件、csr文件和csr扩展文件,生成证书文件(名称为server.crt)

openssl x509 -req -sha256 -days 3650 \
-extfile extfile.cnf \
-extensions v3_req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-out server.crt \
-CAcreateserial

3.4 检验证书文件是否由指定CA签署

openssl verify -CAfile ca.crt -verbose server.crt

在这里插入图片描述


4 编写服务端代码

代码来自博客:https://blog.csdn.net/lf940841989/article/details/16881331

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define MAXBUF 1024

int main(int argc, char **argv)
{
    int sockfd, new_fd;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    char buf[MAXBUF + 1];
    SSL_CTX *ctx;

        printf("Usage: %s port listenno(1) ip publickey privaterkey\n", argv[0]);

        if(argc != 6) {
                printf("parameter error\n");
                exit(0);
        }

    if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;
    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;

    /* SSL 库初始化*/
    SSL_library_init();

    /* 载入所有SSL 算法*/
    OpenSSL_add_all_algorithms();

    /* 载入所有SSL 错误消息*/
    SSL_load_error_strings();

    /* 以SSL V2 和V3 标准兼容方式产生一个SSL_CTX ,即SSL Content Text */
    ctx = SSL_CTX_new(SSLv23_server_method());
    //ctx = SSL_CTX_new(SSLv3_server_method());

    /* 也可以用SSLv2_server_method() 或SSLv3_server_method() 单独表示V2 或V3
     * 标准*/
    if (ctx == NULL) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 载入用户的数字证书, 此证书用来发送给客户端。证书里包含有公钥*/
    if (SSL_CTX_use_certificate_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 载入用户私钥*/
    if (SSL_CTX_use_PrivateKey_file(ctx, argv[5], SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 检查用户私钥是否正确*/
    if (!SSL_CTX_check_private_key(ctx)) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 开启一个socket 监听*/
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    } else
        printf("socket created\n");

    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);

    if (argv[3])
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
            == -1) {
        perror("bind");
        exit(1);
    } else
        printf("binded\n");

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    } else
        printf("begin listen\n");

    while (1) {
        SSL *ssl;
        len = sizeof(struct sockaddr);

        /* 等待客户端连上来*/
        if ((new_fd =
                    accept(sockfd, (struct sockaddr *) &their_addr,
                           &len)) == -1) {
            perror("accept");
            exit(errno);
        } else
            printf("server: got connection from %s, port %d, socket %d\n",
                   inet_ntoa(their_addr.sin_addr),
                   ntohs(their_addr.sin_port), new_fd);

        /* 基于ctx 产生一个新的SSL */
        ssl = SSL_new(ctx);

        /* 将连接用户的socket 加入到SSL */
        SSL_set_fd(ssl, new_fd);

        /* 建立SSL 连接*/
        if (SSL_accept(ssl) == -1) {
            perror("accept");
            close(new_fd);
            break;
        }

        /* 开始处理每个新连接上的数据收发*/
        bzero(buf, MAXBUF + 1);
        strcpy(buf, "server->client");

        /* 发消息给客户端*/
        len = SSL_write(ssl, buf, strlen(buf));
        if (len <= 0) {
            printf
            ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
             buf, errno, strerror(errno));
            goto finish;
        } else
            printf("消息'%s'发送成功,共发送了%d 个字节!\n",
                   buf, len);
        bzero(buf, MAXBUF + 1);

        /* 接收客户端的消息*/
        len = SSL_read(ssl, buf, MAXBUF);
        if (len > 0)
            printf("接收消息成功:'%s',共%d 个字节的数据\n",
                   buf, len);
        else
            printf
            ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
             errno, strerror(errno));
        /* 处理每个新连接上的数据收发结束*/

finish:
        /* 关闭SSL 连接*/
        SSL_shutdown(ssl);

        /* 释放SSL */
        SSL_free(ssl);

        /* 关闭socket */
        close(new_fd);
    }

    /* 关闭监听的socket */
    close(sockfd);

    /* 释放CTX */
    SSL_CTX_free(ctx);

    return 0;
}

5 编写客户端代码

代码来自博客:https://blog.csdn.net/lf940841989/article/details/16881331

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define MAXBUF 1024

void ShowCerts(SSL * ssl)
{
    X509 *cert;
    char *line;
    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) {
        printf("数字证书信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("证书: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("颁发者: %s\n", line);
        free(line);
        X509_free(cert);
    } else
        printf("无证书信息!\n");
}

int main(int argc, char **argv)
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    SSL_CTX *ctx;
    SSL *ssl;

        printf("Usage: %s server_ip server_port\n", argv[0]);

    if (argc != 3) {

        printf("参数格式错误!正确用法,比如: %s 127.0.0.1 80\n"
                 "此程序用来从某个IP 地址的服务器某个端口接收最多MAXBUF 个字节的消息\n",
         argv[0]);

        exit(0);
    }

    /* SSL 库初始化,参看ssl-server.c 代码*/
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ctx = SSL_CTX_new(SSLv23_client_method());
    // ctx = SSL_CTX_new(SSLv3_client_method());
    if (ctx == NULL) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 创建一个socket 用于tcp 通信*/
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }
    printf("socket created\n");

    /* 初始化服务器端(对方)的地址和端口信息*/
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv[2]));
    if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }
    printf("address created\n");

    /* 连接服务器*/
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }
    printf("server connected\n");

    /* 基于ctx 产生一个新的SSL */
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, sockfd);

    /* 建立SSL 连接*/
    if (SSL_connect(ssl) == -1)
        ERR_print_errors_fp(stderr);
    else {
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        ShowCerts(ssl);
    }
    /* 接收对方发过来的消息,最多接收MAXBUF 个字节*/
    bzero(buffer, MAXBUF + 1);

    /* 接收服务器来的消息*/
    len = SSL_read(ssl, buffer, MAXBUF);
    if (len > 0)
        printf("接收消息成功:'%s',共%d 个字节的数据\n",
               buffer, len);
    else {
        printf
        ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
         errno, strerror(errno));
        goto finish;
    }
    bzero(buffer, MAXBUF + 1);
    strcpy(buffer, "from client->server");

    /* 发消息给服务器*/
    len = SSL_write(ssl, buffer, strlen(buffer));
    if (len < 0)
        printf
        ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
         buffer, errno, strerror(errno));
    else
        printf("消息'%s'发送成功,共发送了%d 个字节!\n",
               buffer, len);

finish:
    /* 关闭连接*/
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);
    SSL_CTX_free(ctx);

    return 0;
}


6 编译服务端代码并运行

g++ -o server server_ssl.c -Wl,-rpath,/opt/openssl/lib64 -L/opt/openssl/lib64 -lssl -lcrypto -I/opt/openssl/include
./server 8888 1 127.0.0.1 server.crt server.key

在这里插入图片描述


7 编译客户端代码

g++ -o client client_ssl.c -L/opt/openssl/lib64 -lssl -lcrypto -I/opt/openssl/include -Wl,-rpath,/opt/openssl/lib64

在这里插入图片描述


8 运行客户端

./client 127.0.0.1 8888

客户端的输出如下图:
在这里插入图片描述

客户端会打印服务端传输过来的ssl证书,CA和CN正是本文在创建证书阶段所填写的信息,说明在TCP握手之后,进行SSL握手也是成功的。握手成功后,往服务端发送信息"from client->server"。

此时服务端的输出如下图:
在这里插入图片描述
服务端在SSL握手成功后,通过SSL_read()方法接收来自客户端发送过来的加密信息并自动解密(明文信息为"from client->server")。


9 小结

tcp握手成功后,服务端和客户端代码使用openssl库能加解密tcp中传输的字节序列,保障通信的安全。在代码编写套路上,服务端和客户端代码在拿到fd之后,将fd塞进ssl上下文对象中,使用格式如SSL_XXX的方法来进行SSL握手、读写数据。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软考】位示图

目录 一、基本概念二、位示图 一、基本概念 1.要将文件保存到外部存储器&#xff08;外存或辅存&#xff09;&#xff0c;首先得知道存储空间的使用情况 2.要清楚哪个物理块已经被占用&#xff0c;哪个物理块是空闲的 3.当对大容量的磁盘存储空间被多用户共享时&#xff0c;用户…

套接字通信(附带单线程TCP套接字通信代码)

套接字-Socket 1. 概念 1.1 局域网和广域网 局域网&#xff08;LAN&#xff09;和广域网&#xff08;WAN&#xff09;是两种不同范围的计算机网络&#xff0c;它们用于连接多台计算机以实现数据共享和通信。 局域网&#xff08;LAN&#xff09;&#xff1a; 定义&#xff1…

JUC-CAS

1. CAS概述 CAS(Compare ans swap/set) 比较并交换&#xff0c;实现并发的一种底层技术。它将预期的值和内存中的值比较&#xff0c;如果相同&#xff0c;就更新内存中的值。如果不匹配&#xff0c;一直重试&#xff08;自旋&#xff09;。Java.util.concurrent.atomic包下的原…

AMIS的组件学习使用

部分代码片段 {"id": "filterForm","className": " xysd-zbkb-pubquery","labelWidth": 130,"body": [{"type": "grid","className": "xysd-grid-query-input","c…

鲲鹏微认证——openEuler开源操作系统迁移实践

文章目录 为什么要系统搬迁为什么选择欧拉欧拉系统迁移概述实施路径工具实战 为什么要系统搬迁 2020年12月&#xff0c;CentOs作为由开源社区免费提供的操作系统&#xff0c;宣布将对CentO58于2021年底停止服务&#xff0c;CentO57则于2024年6月底停止服务。 这将直接导致操作…

MyBatis详解(1)-- ORM模型

MyBatis详解&#xff08;1&#xff09; JDBC的弊端&#xff1a; ORM 模型常见的ORM模型&#xff1a;mybatis和Hibernate的区别 ***优势&#xff1a;mybatis解决问题&#xff1a;优点&#xff1a; MyBatisMyBatis环境搭建项目架构mybatis生命周期 JDBC的弊端&#xff1a; 1.硬编…

dataGrip连接数据库mysql和intersystems的iris

intersystems公司的产品iris是cache的升级版本&#xff0c;目前绝大多数数据库工具都没法连接这个数据库 datagrip下载地址 https://download-cdn.jetbrains.com.cn/datagrip/datagrip-2023.3.3.exe 选择对应的数据库产品类型 新建数据库资源连接 填上对应的数据库连接和账…

12.前端--CSS-背景属性

1.背景颜色 样式名称&#xff1a; background-color 定义元素的背景颜色 使用方式: background-color:颜色值; 其他说明&#xff1a; 元素背景颜色默认值是 transparent&#xff08;透明&#xff09;      background-color:transparent; 代码演示&#xff1a; 背景色…

将AWS iot消息数据发送Kinesis Firehose Stream存向S3

观看此文章之前&#xff0c;请先学习AWS iot的数据收集&#xff1a; 使用Linux SDK客户端向AWS Iot发送数据-CSDN博客 1、工作原理&#xff1a; 1.1 规则 规则可让您的设备与 AWS 服务进行交互。分析规则并根据物品发送的消息执行操作。您可以使用规则来支持任务&#xff0…

Lucene 源码分析——BKD-Tree

Lucene 源码分析——BKD-Tree - AIQ Bkd-Tree Bkd-Tree作为一种基于K-D-B-tree的索引结构&#xff0c;用来对多维度的点数据(multi-dimensional point data)集进行索引。Bkd-Tree跟K-D-B-tree的理论部分在本篇文章中不详细介绍&#xff0c;对应的两篇论文在附件中&#xff0c…

配置ntp时间服务器和ssh免密登录实验

1&#xff1a;配置ntp时间服务器&#xff0c;确保客户端主机能和服务主机同步时间 第一步&#xff0c;将服务器的时间同步对象改为阿里的时间服务器&#xff08;这样比较精准&#xff09; 先启动服务&#xff1a;[rootserver ~]# systemctl start chronyd 进入配置文件&#xf…

源 “MySQL 5.7 Community Server“ 的 GPG 密钥已安装,但是不适用于此软件包。请检查源的公钥 URL 是否配置正确

Is this ok [y/d/N]: y Downloading packages: 警告&#xff1a;/var/cache/yum/x86_64/7/mysql57-community/packages/mysql-community-server-5.7.44-1.el7.x86_64.rpm: 头V4 RSA/SHA256 Signature, 密钥 ID 3a79bd29: NOKEY 从 file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql 检…

知识图谱符号表示比较:特性图、RDF和OWL

目录 前言1 特性图&#xff1a;灵活的图结构表示1.1 优势与灵活性1.2 存储优化与查询优势1.3 挑战&#xff1a;缺乏工业标准支持 2 RDF&#xff08;Resource Description Framework&#xff09;&#xff1a;面向Web的数据标准2.1 三元组结构的优势2.2 语义标准与词汇丰富性2.3 …

蓝桥杯备战——1.点亮LED灯

1.解析原理图 由上图可以看到8个共阳LED灯接到了573输出口&#xff0c;而573输入接到单片机P0口上。当573 LE脚输入高电平时&#xff0c;输出随输入变化&#xff0c;当LE为低电平时&#xff0c;输出锁存。 由上图可以看到Y4C接到了或非门74HC02的输出端&#xff0c;而输入端为…

顺序表的增、删、改、查

小伙伴们好&#xff0c;学完C语言&#xff0c;就要开始学数据结构了&#xff0c;数据结构也是非常重要的&#xff0c;今天我们主要来学习在数据结构中最常用的增删改查操作。话不多说&#xff0c;一起来学习吧 1.数据结构相关概念 1.什么是数据结构&#xff1f; 数据结构是由…

【Web前端开发基础】CSS3之Web字体、字体图标、平面转换、渐变

CSS3之Web字体、字体图标、平面转换、渐变 目录 CSS3之Web字体、字体图标、平面转换、渐变一、Web字体1.1 Web字体概述1.2 字体文件1.3 font-face 规则 二、字体图标2.1 字体图标2.2 字体图标的优点2.3 图标库2.4 下载字体包2.5 字体图标的使用步骤2.6 字体图标使用注意点2.7 上…

12.常用统计分析方法——聚类分析

目录 基础知识 实操 层次聚类 划分聚类 方法一&#xff1a;K均值聚类&#xff08;最常见&#xff09; 方法二&#xff1a;基于中心点的划分&#xff08;PAM&#xff09; 避免不存在的类 基础知识 概念&#xff1a; 聚类分析是一种数据归约技术&#xff0c;旨在揭露一个…

prometheus监控RabbitMQ策略

一般用官方的rabbitmq_exporter采取数据即可&#xff0c;然后在普米配置。但如果rabbitmq节点的队列数超过了5000&#xff0c;往往rabbitmq_exporter就会瘫痪&#xff0c;因为rabbitmq_exporter采集的信息太多&#xff0c;尤其是那些队列的细节&#xff0c;所以队列多了&#x…

vue3-深入组件-组件注册和props更多细节

组件注册 定义好的组件需要注册才能被使用。 注册方式有两种 全局注册 局部注册 全局注册 .component() 方法&#xff0c;让组件在当前 Vue 应用中全局可用。 在 main.ts 中 import ./assets/main.cssimport { createApp } from vue import { createPinia } from pinia i…

无人机航迹规划(五):七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划(提供MATLAB代码)

一、七种算法&#xff08;DBO、LO、SWO、COA、LSO、KOA、GRO&#xff09;简介 1、蜣螂优化算法DBO 蜣螂优化算法&#xff08;Dung beetle optimizer&#xff0c;DBO&#xff09;由Jiankai Xue和Bo Shen于2022年提出&#xff0c;该算法主要受蜣螂的滚球、跳舞、觅食、偷窃和繁…