20.5 OpenSSL 套接字RSA加密传输

news2024/11/27 8:38:21

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指定密钥对长度1024
  pRSA = 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待发送字符串长度,填充好以后首先运行服务端,接着运行客户端,此时数据包将会被加密传输,在对端解密并输出如下图所示的结果;

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

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

相关文章

C/C++与圆相关的计算 2021年3月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C与圆相关的计算 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C与圆相关的计算 2021年3月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给出圆的半径&#xff0c;求圆的直径、周长…

网络套接字编程(三)

网络套接字编程(三) 文章目录 网络套接字编程(三)简易日志组件引入日志的原因日志等级打印日志函数将日志组件使用到服务端中 守护进程概念进程组、终端、会话守护进程的实现原理守护进程化组件将守护进程化组件使用到服务端中 补充知识关于inet_ntoa 在上一篇博客 网络套接字…

Required String parameter ‘name‘ is not present

[org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter name is not present] 服务端有参数name&#xff0c;客户端没有传上来

Java--网络通信

1.端口Port&#xff1a; 通常计算机上提供了HTTP,FTP等多种服务&#xff0c;客户机通过不同的端口来确定连接到服务器的哪项服务上。 2.套接字Socket&#xff1a; 套接字Socket用于将应用程序与端口连接起来。套接字是一个假想的链接装置。 3.InetAddress类 java.net包中的Ine…

一文带你轻松拿下Java中的抽象类

&#x1f937;‍♀️&#x1f937;‍♀️&#x1f937;‍♀️各位看官你们好呀&#xff01;&#xff01;&#xff01; 今天我带大家来深入了解一下Java中的抽象类&#xff0c;相信看完这篇文章&#xff0c;你将会有很大的收获&#xff01; 个人主页 &#x1f302;c/java领域新星…

23种设计模式(创建型、构造型、行为型)

目录 设计模式一、创建型设计模式1.1、简单工厂模式(SimpleFactory)1.2、工厂方法&#xff08;Factory Method&#xff09;1.3、 抽象工厂&#xff08;Abstarct Factory&#xff09;1.4、生成器模式&#xff08;Builder&#xff09;1.5、 原型模式&#xff08;Prototype&#x…

轻量封装WebGPU渲染系统示例<12>- 基础3D对象实体(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/PrimitiveEntityTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调用隔…

Spark的主要概念

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f34a; 1. RDD&#x1f34a; 2. Spark SQL&#x1f34a; 3. Spark Streaming&#x1f34a; 4. MLlib&#x1f34a; 5. GraphX&#x1f34a; 总结 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍…

python-根据关键词匹配连续的内容

运用PyQt5生成可执行小程序&#xff1a;匹配起始关键词到截止关键词区间的GGA格式的内容&#xff0c;支持多选文件&#xff0c;并清除过程中产生的复制文件。 GGA文件如下&#xff1a; $GPZDA,063052.00,16,10,2023,,*61 $GPGGA,063052.00,4349.7377413,N,12509.8354912,E,4,…

Swift语言配合HTTP写的一个爬虫程序

下段代码使用Embassy库编写一个Swift爬虫程序来爬取jshk的内容。我会使用proxy_host为duoip&#xff0c;proxy_port为8000的爬虫IP服务器。 使用Embassy库编写一个Swift爬虫程序可以实现从网页上抓取数据的功能。下面是一个简单的步骤&#xff1a; 1、首先&#xff0c;需要在X…

JMM讲解

一&#xff1a;为什么要有JMM&#xff0c;它为什么出现&#xff1f; CPU的运行并不是直接操作内存而是先把内存里面的数据读到缓存&#xff0c;而内存的读和写操作的时候会造成不一致的问题。JVM规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异&…

MongoDB安装及开发系例全教程

一、系列文章目录 一、MongoDB安装教程—官方原版 二、MongoDB 使用教程(配置、管理、监控)_linux mongodb 监控 三、MongoDB 基于角色的访问控制 四、MongoDB用户管理 五、MongoDB基础知识详解 六、MongoDB—Indexs 七、MongoDB事务详解 八、MongoDB分片教程 九、Mo…

基于nodejs+vue 网上商城系统系统-毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

mediasoup webrtc音视频会议搭建

环境ubuntu22.10 nvm --version 0.33.11 node -v v16.20.2 npm -v 8.19.4 node-gyp -v v10.0.1 python3 --version Python 3.10.7 python with pip: sudo apt install python3-pip gcc&g version 12.2.0 (Ubuntu 12.2.0-3ubuntu1) Make 4.2.1 npm install mediasoup3 sudo …

pytorch+LSTM实现使用单参数预测,以及多参数预测(代码注释版)

开发前准备&#xff1a; 环境管理&#xff1a;Anaconda python: 3.8 显卡&#xff1a;NVIDIA3060 pytorch: 到官网选择conda版本&#xff0c;使用的是CUDA11.8 编译器&#xff1a; PyCharm 简述&#xff1a; 本次使用seaborn库中的flights数据集来做试验&#xff0c;我们通过…

AI:52-基于深度学习的垃圾分类

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

基于单片机控制的GSM短信模块家庭防盗报警系统

博主主页&#xff1a;单片机辅导设计 博主简介&#xff1a;专注单片机技术领域和毕业设计项目。 主要内容&#xff1a;毕业设计、简历模板、学习资料、技术咨询。 文章目录 主要介绍一、内容1 设计任务和要求1 主要内容 二、系统总体方案2.1 系统整体设计思路2.2 系统方案设计 …

抛弃繁琐、提高效率:低代码工具助你飞速开发 | 开源专题 No.42

supabase/supabase Stars: 56.9k License: Apache-2.0 Supabase 是一个开源的 Firebase 替代品&#xff0c;使用企业级开源工具构建了 Firebase 的功能。其主要功能包括&#xff1a; 托管 Postgres 数据库身份验证和授权自动生成 API (支持 REST 和 GraphQL)实时订阅函数 (包…

阿里云盘第三方linux客户端“小白羊”云盘“Aria2本地连接已断开”错误的解决方法

简介 随着数据的不断增长&#xff0c;我们需要更大的存储空间来保存我们的信息。阿里云盘是阿里巴巴推出的一款云存储服务&#xff0c;它提供了大量可扩展的存储空间。然而&#xff0c;阿里云盘官方没有提供Linux操作系统的客户端。 在这种情况下&#xff0c;“小白羊”云盘…