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

2024-01-25 05:12

本文主要是介绍基于openssl v3搭建ssl安全加固的c++ tcpserver,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 1024int 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]);elsemyport = 7838;if (argv[2])lisnum = atoi(argv[2]);elselisnum = 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);} elseprintf("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]);elsemy_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))== -1) {perror("bind");exit(1);} elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {perror("listen");exit(1);} elseprintf("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);} elseprintf("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;} elseprintf("消息'%s'发送成功,共发送了%d 个字节!\n",buf, len);bzero(buf, MAXBUF + 1);/* 接收客户端的消息*/len = SSL_read(ssl, buf, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d 个字节的数据\n",buf, len);elseprintf("消息接收失败!错误代码是%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 1024void 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);} elseprintf("无证书信息!\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));elseprintf("消息'%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握手、读写数据。

这篇关于基于openssl v3搭建ssl安全加固的c++ tcpserver的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/642186

相关文章

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

Python极速搭建局域网文件共享服务器完整指南

《Python极速搭建局域网文件共享服务器完整指南》在办公室或家庭局域网中快速共享文件时,许多人会选择第三方工具或云存储服务,但这些方案往往存在隐私泄露风险或需要复杂配置,下面我们就来看看如何使用Py... 目录一、android基础版:HTTP文件共享的魔法命令1. 一行代码启动HTTP服务器2. 关键参