20.5 OpenSSL 套接字RSA加密传输
RSA算法同样可以用于加密传输,但此类加密算法虽然非常安全,但通常不会用于大量的数据传输,这是因为RSA
算法加解密过程涉及大量的数学运算,尤其是模幂运算(即计算大数的幂模运算),这些运算对于计算机而言是十分耗时。
其次在RSA
算法中,加密数据的长度不能超过密钥长度减去一定的填充长度。一般情况下,当RSA密钥长度为1024
位时,可以加密长度为128
字节,密钥长度为2048
位时,可以加密长度为245
字节;当密钥长度为3072
位时,可以加密长度为371
字节。因此,如果需要加密的数据长度超过了密钥长度允许的范围,可以采用分段加密的方法。我们可以将数据包切割为每个128
个字符,这样就可以实现循环传输大量字符串。
20.5.1 加解密算法封装
在之前的章节中我们都是使用命令行的方式手动生成密钥对文件,其实在OpenSSL
中我们完全可以使用SDK
提供的函数自动生成对应的加密密钥对文件,如下一段代码中,CreateRSAPEM
则是一个生成密钥对的函数,分别向该函数内传递一个公钥,私钥,以及数据长度,即可得到两个RSA文件。
#include <iostream>
#include <string>
#include <Windows.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>extern "C"
{
#include <openssl/applink.c>
}#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")// 生成RSA公钥和私钥文件
BOOL CreateRSAPEM(char *PublicKey, char *PrivateKey, int KeySize)
{BIO* bpub, *bpri;// 分别新建密钥对文件bpub = BIO_new_file(PublicKey, "w");bpri = BIO_new_file(PrivateKey, "w");if (!bpub || !bpri){return FALSE;}RSA* pRSA = 0;// 生成密钥对KeySize指定密钥对长度1024pRSA = RSA_generate_key(KeySize, RSA_F4, NULL, NULL);if (pRSA != NULL){// 写出公钥if (!PEM_write_bio_RSAPublicKey(bpub, pRSA)){return FALSE;}// 写出私钥if (!PEM_write_bio_RSAPrivateKey(bpri, pRSA, NULL, NULL, 0, NULL, NULL)){return FALSE;}}if (bpub){BIO_free(bpub);}if (bpri){BIO_free(bpri);}if (pRSA){RSA_free(pRSA);}return TRUE;
}int main(int argc, char* argv[])
{// 生成公钥与私钥BOOL flag = CreateRSAPEM("public.rsa","private.rsa",1024);if (flag == TRUE){printf("[*] 已生成密钥对 \n");}system("pause");return 0;
}
代码运行后会分别在当前目录下生成public.rsa
公钥及private.rsa
私钥两个文件,如下图所示;
接着就是对加解密函数的封装实现,为了能更好的实现网络传输,如下是封装的四个函数,其中public_rsa_encrypt
用于使用公钥对字符串进行加密,private_rsa_decrypt
函数使用私钥对字符串进行解密,private_rsa_encrypt
使用私钥加密,public_rsa_decrypt
使用公钥解密,读者可根据自己的实际需求选择不同的加解密函数。
// 使用公钥加密
BOOL public_rsa_encrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return FALSE;}if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return FALSE;}rsa_len = RSA_size(p_rsa);if (RSA_public_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return FALSE;}RSA_free(p_rsa);fclose(file);return TRUE;
}// 使用私钥解密
BOOL private_rsa_decrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return FALSE;}if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return FALSE;}rsa_len = RSA_size(p_rsa);if (RSA_private_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return FALSE;}RSA_free(p_rsa);fclose(file);return TRUE;
}// 使用私钥加密
BOOL private_rsa_encrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return FALSE;}if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return FALSE;}rsa_len = RSA_size(p_rsa);if (RSA_private_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return FALSE;}RSA_free(p_rsa);fclose(file);return TRUE;
}// 使用公钥解密
BOOL public_rsa_decrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return FALSE;}if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return FALSE;}rsa_len = RSA_size(p_rsa);if (RSA_public_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return FALSE;}RSA_free(p_rsa);fclose(file);return TRUE;
}
当我们需要使用公钥加密时可以调用public_rsa_encrypt
函数并依次传入加密前的字符串,公钥路径以及加密后的存储位置,当需要解密时则调用private_rsa_decrypt
函数实现对加密字符串的解密操作,使用代码如下所示;
int main(int argc, char* argv[])
{char text[128] = "hello lyshark";char public_key[] = "d://public.rsa";char encry[128] = { 0 };char private_key[] = "d://private.rsa";char decry[128] = { 0 };// 公钥加密if (public_rsa_encrypt(text, public_key, encry)){printf("[公钥加密] 加密长度: %d \n", strlen((char*)encry));}// 私钥解密if (private_rsa_decrypt(encry, private_key, decry)){printf("[私钥解密] 解密长度: %d \n", strlen((char*)decry));}printf("解密数据: %s \n", decry);system("pause");return 0;
}
读者可自行编译并运行上述代码,即可看到加解密数据输出,如下图所示;
将这个流程反过来使用,使用私钥对数据进行加密,使用公钥实现解密,代码如下所示;
int main(int argc, char* argv[])
{char text[128] = "hello lyshark";char public_key[] = "d://public.rsa";char encry[128] = { 0 };char private_key[] = "d://private.rsa";char decry[128] = { 0 };// 私钥加密if (private_rsa_encrypt(text, private_key, encry)){printf("[私钥加密] 加密长度: %d \n", strlen((char*)encry));}// 公钥解密if (public_rsa_decrypt(encry, public_key, decry)){printf("[公钥解密] 解密长度: %d \n", strlen((char*)decry));}printf("解密数据: %s \n", decry);system("pause");return 0;
}
私钥加密公钥解密,输出效果图如下所示;
20.5.2 加密传输字符串
当具备了上述加解密函数实现流程后,接下来就可以实现针对字符串的加密传输功能了,因为我们采用的是1024
位的密钥所以每次只能传输128
个字符,为了能传输大量字符则需要对字符进行分块,通过CutSplit()
函数将字符串每100
个字符切割一次,然后在客户端中先使用公钥对其进行加密,加密后分块每次传输一批次的加密数据即可,直到将完整的字符串发送完成为止。
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>extern "C"
{
#include <openssl/applink.c>
}#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")// 使用公钥加密
int public_rsa_encrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return 0;}if ((p_rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return 0;}rsa_len = RSA_size(p_rsa);if (RSA_public_encrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return 0;}RSA_free(p_rsa);fclose(file);return 1;
}// 实现对字符串指定位置进行剪切
char* Cut(char* buffer, int offset, int length)
{char Split[100] = { 0 };memset(Split, 0, 100);strncpy(Split, buffer + offset, length);return Split;
}// 循环剪切字符串
int CutSplit(char* buf, char len, OUT char Split[][1024])
{int count = 0;// 每次剪切len大小for (int x = 0; x < strlen(buf); x += len){char* ref = Cut(buf, x, len);strcpy(Split[count], ref);count += 1;}return count;
}int main(int argc, char* argv[])
{char buf[8192] = "The National Aeronautics and Space Administration is America.";WSADATA WSAData;// 初始化套接字库if (WSAStartup(MAKEWORD(2, 0), &WSAData)){return 0;}// 建立Socket套接字SOCKET client_socket;client_socket = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in ClientAddr;ClientAddr.sin_family = AF_INET;ClientAddr.sin_port = htons(9999);ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 连接到服务端if (connect(client_socket, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr)) != SOCKET_ERROR){char SplitArray[100][1024] = { 0 };// 切割字符串,每100个字符切割一次int count = CutSplit(buf, 100, SplitArray);// 发送发包次数std::cout << "发包次数: " << count << std::endl;char send_count[1024] = { 0 };sprintf(send_count, "%d", count);send(client_socket, send_count, strlen(send_count), 0);// 循环发送数据包for (int x = 0; x < count; x++){std::cout << "原始数据包: " << SplitArray[x] << std::endl;char public_key[] = "d://public.rsa";char encry[1024] = { 0 };// 公钥加密if (public_rsa_encrypt(SplitArray[x], public_key, encry)){std::cout << "RSA 加密长度: " << strlen((char*)encry) << std::endl;}// 发送加密后的数据包send(client_socket, encry, 1024, 0);memset(buf, 0, sizeof(buf));memset(encry, 0, sizeof(encry));}closesocket(client_socket);WSACleanup();}system("pause");return 0;
}
而对于服务端
代码实现部分则需要与客户端保持一致,服务端发送多少次客户端就接收多少次,首先服务端接收需要接收的数据包次数,并以此作为循环条件使用,通过不间断的循环接受数据包,并调用private_rsa_decrypt
完成数据包的解密工作,最终将数据包拼接成recv_message_all
并输出完整包。
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>extern "C"
{
#include <openssl/applink.c>
}#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")// 使用私钥解密
int private_rsa_decrypt(char* in, char* key_path, char* out)
{RSA* p_rsa;FILE* file;int rsa_len;if ((file = fopen(key_path, "r")) == NULL){return 0;}if ((p_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL)) == NULL){ERR_print_errors_fp(stdout);return 0;}rsa_len = RSA_size(p_rsa);if (RSA_private_decrypt(rsa_len, (unsigned char*)in, (unsigned char*)out, p_rsa, RSA_NO_PADDING) < 0){return 0;}RSA_free(p_rsa);fclose(file);return 1;
}int main(int argc, char* argv[])
{WSADATA WSAData;// 初始化套接字库if (WSAStartup(MAKEWORD(2, 0), &WSAData)){return 0;}// 建立Socket套接字SOCKET server_socket;server_socket = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in ServerAddr;ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(9999);ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 绑定套接字bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));listen(server_socket, 10);SOCKET message_socket;// 接收并拼接数据char recv_message_all[8109] = { 0 };if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) != INVALID_SOCKET){// 接收需要获取的次数char recv_count[1024] = { 0 };recv(message_socket, recv_count, 1024, 0);std::cout << "收包次数: " << recv_count << std::endl;for (int x = 0; x < atoi(recv_count); x++){// 接收加密后的数据包char buf[1024] = { 0 };recv(message_socket, buf, 1024, 0);// 私钥解密char private_key[] = "d://private.rsa";char decry[1024] = { 0 };// 调用解密函数if (private_rsa_decrypt(buf, private_key, decry)){std::cout << "RSA 解密长度: " << strlen((char*)decry) << std::endl;}std::cout << "RSA 解密数据包: " << decry << std::endl;// 组合数据包strCut(recv_message_all, decry);memset(buf, 0, sizeof(buf));memset(decry, 0, sizeof(decry));}closesocket(message_socket);// 输出最终数据包std::cout << std::endl;std::cout << "组合数据包: " << recv_message_all << std::endl;}closesocket(server_socket);WSACleanup();system("pause");return 0;
}
读者可自行填充客户端中的buf
待发送字符串长度,填充好以后首先运行服务端,接着运行客户端,此时数据包将会被加密传输,在对端解密并输出如下图所示的结果;
相关文章:

20.5 OpenSSL 套接字RSA加密传输
RSA算法同样可以用于加密传输,但此类加密算法虽然非常安全,但通常不会用于大量的数据传输,这是因为RSA算法加解密过程涉及大量的数学运算,尤其是模幂运算(即计算大数的幂模运算),这些运算对于计…...
C#中的19个LINQ to XML 类
System.Xml.Linq 命名空间包含 LINQ to XML 的19个类。 LINQ to XML 是内存中的 XML 编程接口,使能轻松有效地修改 XML 文档。 微软在 LINQ 上投入了很大的精力,使我们在编程时感觉到很舒服。处理 XML 时使用最多的三个类:XElement、XAttribu…...

取消elementUI中table的选中状态和勾选状态赋值
一、取消所有选中 1、表格上绑定ref 2、清空用户选中数据 this.$refs.loopRef.clearSelection()二、勾选状态赋值 获取数据,flag为true则是选中状态,并将前面勾选框设为选中状态 this.listData.forEach(item> {if(row.flag1){this.$refs.loopRef.to…...
LeetCode 72. 编辑距离(动态规划)
题目: 链接:LeetCode 72. 编辑距离 难度:中等 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符删除一个字符替换一个字符 示例…...

Bytedance揭秘OpenAI大模型: GPT-3到GPT-4进化路径
文章目录 探秘GPT-3到GPT-4进化之路1、SFT:早期GPT进化的推动者2、RLHF和SFT:编码能力提升的功臣3、代码加入预训练,对推理帮助最大4、“跷跷板”现象 论文地址项目链接Reference GPT-Fathom: Benchmarking Large Language Models to Deciphe…...

第二十六章 BEV感知系列三(车道线感知)
前言 近期参与到了手写AI的车道线检测的学习中去,以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新,力求完整精炼,引人启示。所需前期知识,可以结合手写AI进行系统的学习。 BEV感知系列是对论文Delving into the De…...
总结几个面试题
目录 1. this 指针存在哪里 2. this指针可以为空吗? 3. 结构体怎么对齐?为什么要进行内存对齐? 4. 如何让结构体按照指定的对齐方式对齐?能否按照3、4、5即任意字节对齐? 5. 什么是大小端?如何测…...

【多线程】并发问题
public class BuyTicket implements Runnable{private int ticketNums10;Overridepublic void run() {for(int i1;i<ticketNums;i){if(ticketNums<0){break;}System.out.println(Thread.currentThread().getName() "抢到了第" i "张票");ticketNu…...

httpclient工具类(支持泛型转换)
1、网上搜到的httpclient工具类的问题: 1.1、如下图我们都能够发现这种封装的问题: 代码繁杂、充斥了很多重复性代码返回值单一,无法拿到对应的Java Bean对象及List对象集合实际场景中会对接大量第三方的OPEN API,下述方法的扩展…...
【华为OD题库-003】最佳植树距离-Java
题目 小明在直线的公路上种树,现在给定可以种树的坑位的数星和位置,以及需要种多少棵树苗,问树苗之间的最小间距是多少时,可以保证种的最均匀(两棵树苗之间的最小间距最大) 输入描述 输入三行: 第一行一个整数:坑位的数…...

Oracle(12)Managing Indexes
目录 目标: 一、基础知识 1、Classification ofindexes 索引的分类 2、B-Tree vs Bitmap 3、Creating Indexes: Guidelines 创建索引:准则 4、Offline Index Rebuild 脱机索引重建 5、RebuildingIndexes 重建索引 6、Online Index Rebuild 在线索引重建 7…...

DirectX3D 虚拟现实项目 三维物体的光照及着色(五个不同着色效果的旋转茶壶)
文章目录 任务要求原始代码CPP文件代码着色器文件代码 效果展示 任务要求 本篇文章是中国农业大学虚拟现实课程的一次作业内容,需要对五个茶壶模型使用不同的光照进行着色和渲染,然后旋转展示。 本人的代码也是在其他人的代码的基础上修改来的…...

【Verilog 教程】7.3 Verilog 串行 FIR 滤波器设计
串行 FIR 滤波器设计 设计说明 设计参数不变,与并行 FIR 滤波器参数一致。即,输入频率为 7.5 MHz 和 250 KHz 的正弦波混合信号,经过 FIR 滤波器后,高频信号 7.5MHz 被滤除,只保留 250KMHz 的信号。 输入频率&#x…...
用golang实现一个基于interface的多态示例,展示其使用场景和优劣性。
以下是一个简单的基于interface的多态示例,该示例展示了如何通过使用interface来实现多个不同类型的结构体的共同行为。具体示例如下: package mainimport "fmt"type Animal interface {Speak() string }type Dog struct {Name string }func …...

ArcGIS for Android 禁止地图旋转
ArcGIS for Android 禁止地图旋转 话不多说,直接上代码!!! public class LoadMap extends AppCompatActivity {// 地图private MapView mapView;private ArcGISMap map;Overrideprotected void onCreate(Bundle savedInstanceSta…...

freertos静态创建任务
在开始前先有个小插曲,我的keil的自动补全代码功能使用不了,经过查找是因为之前装51把有的文件覆盖了,照这篇博客就可以解决。 然后之前那份代码我们是动态创建任务,先来说一下动态创建任务和静态创建任务的区别: Fre…...

VBA根据Excel内容快速创建PPT
示例需求:根据Excel中选中的单元格内容(3列)如下图所示,在已打卡的PowerPoint文件中创建页面。 新增PPT Slide页面使用第二个模板页面,其中包含两个文本占位符,和一个图片占位符。将Excel选中区域中前两列写…...
服务器操作系统有哪些
服务器操作系统有哪些 电脑想要运行就离不开操作系统,而服务器想要正常运行同样也离不开操作系统,那你知道服务器系统有哪些?服务器系统与电脑系统有什么区别?这些问题就由壹基比小鑫在下文中来告诉大家。 服务器系统有哪些&…...

泄漏检测与修复(LDAR)过程管控平台(销售出租)VOCs便携式总烃分析仪(销售出租)
LDAR是Leak Detection and Repair(泄漏检测与修复)的缩写,也是国际上较先进的化工废气检测技术。LDAR主要通过检测化工企业原料输送管道、泵、阀门、法兰等易产生易产生挥发性有机物(简称VOCs)泄漏的部位,并…...

VueX 模块化和namespace
当我们的项目很大的时候,VueX中的代码会越来越多,会有处理数据的,处理人员列表的,处理订单的... 如果我们将这些东西都写在一个state、actions和mutations中的话,就非常不方便后期的维护。 所以我们引入了VueX的模块…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...