基于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

相关文章

SpringBoot快速搭建TCP服务端和客户端全过程

《SpringBoot快速搭建TCP服务端和客户端全过程》:本文主要介绍SpringBoot快速搭建TCP服务端和客户端全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录TCPServerTCPClient总结由于工作需要,研究了SpringBoot搭建TCP通信的过程

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强

C++迭代器失效的避坑指南

《C++迭代器失效的避坑指南》在C++中,迭代器(iterator)是一种类似指针的对象,用于遍历STL容器(如vector、list、map等),迭代器失效是指在对容器进行某些操作后... 目录1. 什么是迭代器失效?2. 哪些操作会导致迭代器失效?2.1 vector 的插入操作(push_back,

Gradle下如何搭建SpringCloud分布式环境

《Gradle下如何搭建SpringCloud分布式环境》:本文主要介绍Gradle下如何搭建SpringCloud分布式环境问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Gradle下搭建SpringCloud分布式环境1.idea配置好gradle2.创建一个空的gr

Linux搭建单机MySQL8.0.26版本的操作方法

《Linux搭建单机MySQL8.0.26版本的操作方法》:本文主要介绍Linux搭建单机MySQL8.0.26版本的操作方法,本文通过图文并茂的形式给大家讲解的非常详细,感兴趣的朋友一起看看吧... 目录概述环境信息数据库服务安装步骤下载前置依赖服务下载方式一:进入官网下载,并上传到宿主机中,适合离线环境

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指