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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

想学计算机编程从什么学起?零基础如何自学计算机编程?中文编程开发语言工具箱之渐变标签组构件

想学计算机编程从什么学起&#xff1f;零基础如何自学计算机编程&#xff1f; 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;…

【每日一题】数组中两个数的最大异或值

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希集合 其他语言python3 写在最后 Tag 【哈希集合】【位运算-异或和】【数组】【2023-11-04】 题目来源 421. 数组中两个数的最大异或值 题目解读 找出数组中两个数的最大异或结果。 解题思路 一看数据量达到了 …

【深度学习基础】Pytorch框架CV开发(1)基础铺垫

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

Docker的简单安装

安装环境 CentOS Linux release 8.1.1911 (Core)内核4.18.0-147.el8.x86_64Mini Installation 安装前的准备工作 切换国内源 由于centos源已经过期&#xff0c;所以切换为阿里云的yum源&#xff0c;第二个是docker的仓库 wget -O /etc/yum.repos.d/CentOS-Base.repo https:…

vue需求:实现签章/签字在页面上自由定位的功能(本质:元素在页面上的拖拽)

目录 第一章 效果展示 第二章 了解工具 2.1 draggable 2.1.1 了解draggable 2.1.2 draggable方法 2.1.3 利用例子理解方法 第三章 效果实现 3.1 实现思路 3.2 代码实现 3.2.1 涉及到的点 3.2.2 源代 第一章 效果展示 效果描述&#xff1a;通过点击左边栏的签名和…

C#,数值计算——积分方程与逆理论,构造n点等间隔求积的权重的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 构造n点等间隔求积的权重 /// Constructs weights for the n-point equal-interval quadrature /// from O to(n-1)h of a function f(x) times an arbitrary /// (pos…

十年JAVA搬砖路——Linux搭建Ldap服务器。

1.安装命令 yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel2.启动ldap systemctl start slapd systemctl enable slapd3.修改密码 slappasswd Aa123456获得返回的密码加密密码串&#xff1a; {SSHA}DkSw0…

免费(daoban)gpt,同时去除广告

一. 内容简介 免费(daoban)gpt&#xff0c;同时去除广告&#xff0c;https://chat18.aichatos.xyz/&#xff0c;也可当gpt用&#xff0c;就是有点广告&#xff0c;大家也可以支持一下 二. 软件环境 2.1 Tampermonkey 三.主要流程 3.1 创建javascript脚本 点击添加新脚本 …

2023第二届全国大学生数据分析大赛A题思路

某电商平台用户行为分析与挖掘 背景&#xff1a;电商是当今用户最大的交易市场之一&#xff0c;电商行业也逐渐成熟&#xff0c; 所有市场中可售卖的商品全都在平台中存在&#xff0c;并且在网络和疫情的影 响下&#xff0c;在线上的消费行为满足全年龄段用户。 用户的交易行为…

2023.11.4 Idea 配置国内 Maven 源

目录 配置国内 Maven 源 重新下载 jar 包 配置国内 Maven 源 <mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf> …

为你整理了一份抖音小店的高分打造指南

抖音小店是一种在抖音平台上运营的电商店铺。通过打造一个高分店铺&#xff0c;可以吸引更多用户关注和购买&#xff0c;提升销售业绩。下面四川不若与众将介绍一些打造高分店铺的方法。 首先&#xff0c;店铺名称和简介要吸引眼球。店铺名称应该简洁明了&#xff0c;容易被记住…

curl(六)DNS解析、认证、代理

一 DNS解析 ① ip协议 使用ipv4 [-4] 还是ipv6 [-6] ② --resolve 场景&#xff1a; 在不修改系统配置文件 /etc/hosts 的情况下将单个请求临时固定到 ip 地址 1、使用 * 作为通配符,这样请求中调用的所有 Host 都 会转到你指定的 ip curl https://www.wzj.com --resolv…

王道p18 6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同(c语言代码实现)

视频讲解在这里&#xff1a;&#x1f447; 顺序表p18 第6题wd数据结构课后代码题&#xff08;c语言代码实现&#xff09;_哔哩哔哩_bilibili 本题代码如下 void deleterepeat(struct sqlist* L) {if (L->length 0)printf("表空");int i 0;int k 0;for (i 1…

【软著写作】软著写作过程记录

文章目录 整体流程图&#xff1a;写在前面&#xff1a;一、准备材料1 准备材料2 申请盖章 二、软件登记1 注册账号2 填报软著 整体流程图&#xff1a; 写在前面&#xff1a; 这两天填报了一篇软著&#xff0c;正好将以前第一次填报时&#xff0c;踩的一些坑和过程记录了一下&am…

破解密码 LLM(代码LLM如何从 RNN 发展到 Transformer)

舒巴姆阿加瓦尔 一、说明 近年来&#xff0c;随着 Transformer 的引入&#xff0c;语言模型发生了显着的演变&#xff0c;它彻底改变了我们执行日常任务的方式&#xff0c;例如编写电子邮件、创建文档、搜索网络甚至编码方式。随着研究人员在代码智能任务中应用大型语言模型&am…

[每周一更]-(第70期):常用的GIT操作命令

1、增删文件 # 添加当前目录的所有文件到暂存区 $ git add .# 添加指定文件到暂存区 $ git add <file1> <file2> ...# 添加指定目录到暂存区&#xff0c;包括其子目录 $ git add <dir># 删除工作区文件&#xff0c;并且将这次删除放入暂存区 $ git rm [file…

Redis中的List类型

目录 List类型的命令 lpush lpushx rpush lrange lpop rpop lindex linsert llen lrem ltrim lset 阻塞命令 阻塞命令的使用场景 1.针对一个非空的列表进行操作 2.针对一个空的列表进行操作 3.针对多个key进行操作. 内部编码 lisi类型的应用场景 存储(班级…

SpringSecurity全家桶 (一) —— 简介

1. 概述 Spring Security 是一个框架&#xff0c;提供针对常见攻击的身份验证、授权和保护。 它为保护命令式和响应式应用程序提供了一流的支持&#xff0c;是保护基于 Spring 的应用程序的事实标准。 2. 了解 shiro&#xff1a; 在之前SSM框架盛行的时代&#xff0c;项目的…

C++入门讲解第一篇

大家好&#xff0c;我是Dark Fire&#xff0c;终于进入了C的学习&#xff0c;我知道面对我的将是什么&#xff0c;就算变成秃头佬&#xff0c;也要把C学好&#xff0c;今天是C入门第一篇&#xff0c;我会尽全力将知识以清晰易懂的方式表达出&#xff0c;希望我们一起加油&#…

奇元大模型通过备案 360自研两大模型均获批

11月4日&#xff0c;三六零(601360.SH&#xff0c;下称“360”)大模型“奇元大模型”通过备案落地。今年9月&#xff0c;“360智脑大模型”已获批面向公众开放。360公司也成为国内首家两个大模型均通过备案的科技企业。 从大模型定位和应用角度来看&#xff0c;奇元大模型具备…