20.6 OpenSSL 套接字分发RSA公钥

news2024/11/26 13:42:35

通过上一节的学习读者应该能够更好的理解RSA加密算法在套接字传输中的使用技巧,但上述代码其实并不算完美的,因为我们的公钥和私钥都必须存储在本地文本中且公钥与私钥是固定的无法做到更好的保护效果,而一旦公钥与私钥泄密则整个传输流程都将会变得不安全,最好的保护效果是RSA密钥在每次通信时都进行变换,依次来实现随机密钥对的功能。

20.6.1 RSA算法封装

要实现这个效果我们就需要封装一套可以在内存中生成密钥对的函数,当需要传输数据时动态的生成密钥对,并将公钥部分通过套接字传输给对应的客户端,当客户端收到公钥后则可以使用该公钥进行通信,此时公钥与私钥全程不会存储为文件,这能极大的提升RSA算法的安全性。

要实现内存传输则首先需要封装实现RSA内存生成密钥对函数GenerateMemoryRSAKeys,以及rsa_encrypt加密函数,rsa_decrypt解密函数,读者可自行理解并使用如下代码片段。

#include <iostream>
#include <Windows.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/crypto.h>

extern "C"
{
#include <openssl/applink.c>
}

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl_static.lib")
#pragma comment(lib,"libcrypto.lib")

// 生成RSA需要的公钥和私钥
BOOL GenerateMemoryRSAKeys(char** private_key, char** public_key, int key_length)
{
  // 生成Key函数
  RSA* keypair = RSA_generate_key(key_length, 3, NULL, NULL);

  BIO* pri = BIO_new(BIO_s_mem());
  BIO* pub = BIO_new(BIO_s_mem());

  // 生成写出私钥
  if (!PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL))
  {
    return FALSE;
  }
  
  // 生成写出公钥
  if (!PEM_write_bio_RSAPublicKey(pub, keypair))
  {
    return FALSE;
  }

  size_t pri_len = BIO_pending(pri);
  size_t pub_len = BIO_pending(pub);

  // 分配内存存储公钥与私钥
  char* prikey = (char*)malloc(pri_len + 1);
  char* pubkey = (char*)malloc(pub_len + 1);

  if (prikey == NULL && pubkey == NULL)
  {
    return FALSE;
  }

  // 将公钥与私钥读入到堆中
  BIO_read(pri, prikey, pri_len);
  BIO_read(pub, pubkey, pub_len);

  *private_key = prikey;
  *public_key = pubkey;

  RSA_free(keypair);
  BIO_free_all(pri);
  BIO_free_all(pub);
  return TRUE;
}

// RSA 加密函数
// type=public 使用公钥加密 type=private 使用私钥加密
BOOL rsa_encrypt(char* pub_key, char* msg, char** encrypt, int* encrypt_len, char *type)
{
  RSA* rsa = NULL;
  BIO* keybio = BIO_new_mem_buf((void*)pub_key, -1);
  char* err = (char*)malloc(130);

  if (keybio == NULL)
  {
    return FALSE;
  }

  // 如果是public则使用公钥加密/如果是private则使用私钥
  if (strcmp(type, "public") == 0)
  {
    // PEM_read_bio_RSA_PUBKEY(keybio, NULL, NULL, NULL)
    if (!(rsa = PEM_read_bio_RSAPublicKey(keybio, NULL, NULL, NULL)))
    {
      return FALSE;
    }
  }
  else if (strcmp(type, "private") == 0)
  {
    // 读取私钥文件
    if (!(rsa = PEM_read_bio_RSAPrivateKey(keybio, NULL, NULL, NULL)))
    {
      return FALSE;
    }
  }

  *encrypt_len = RSA_size(rsa);
  *encrypt = (char*)malloc(4096);

  if (strcmp(type, "public") == 0)
  {
    // 使用公钥加密
    if ((RSA_public_encrypt(strlen(msg) + 1, (unsigned char*)msg, (unsigned char*)*encrypt, rsa, RSA_PKCS1_PADDING)) == -1)
    {
      return FALSE;
    }
  }
  else if (strcmp(type, "private") == 0)
  {
    // 使用私钥加密
    if ((RSA_private_encrypt(strlen(msg) + 1, (unsigned char*)msg, (unsigned char*)*encrypt, rsa, RSA_PKCS1_PADDING)) == -1)
    {
      return FALSE;
    }
  }

  RSA_free(rsa);
  free(err);
  BIO_free_all(keybio);
  return TRUE;
}

// RSA 解密函数
// type=public 使用公钥解密 type=private 使用私钥解密
BOOL rsa_decrypt(char* pri_key, char* msg, char** decrypt, int encrypt_len, char *type)
{
  RSA* rsa = NULL;
  BIO* keybio = BIO_new_mem_buf(pri_key, -1);
  if (keybio == NULL)
  {
    return FALSE;
  }

  // 如果是public则使用公钥解密/如果是private则使用私钥
  if (strcmp(type, "public") == 0)
  {
    // 读入公钥文件
    if (!(rsa = PEM_read_bio_RSAPublicKey(keybio, NULL, NULL, NULL)))
    {
      return FALSE;
    }
  }
  else if (strcmp(type, "private") == 0)
  {
    // PEM_read_bio_RSA_PRIVATE(keybio, NULL, NULL, NULL)
    if (!(rsa = PEM_read_bio_RSAPrivateKey(keybio, NULL, NULL, NULL)))
    {
      return FALSE;
    }
  }

  char* err = (char*)malloc(130);
  *decrypt = (char*)malloc(encrypt_len);

  if (strcmp(type, "public") == 0)
  {
    // 使用公钥解密
    if (RSA_public_decrypt(encrypt_len, (unsigned char*)msg, (unsigned char*)*decrypt, rsa, RSA_PKCS1_PADDING) == -1)
    {
      return FALSE;
    }
  }
  else if (strcmp(type, "private") == 0)
  {
    // 私用私钥解密
    if (RSA_private_decrypt(encrypt_len, (unsigned char*)msg, (unsigned char*)*decrypt, rsa, RSA_PKCS1_PADDING) == -1)
    {
      return FALSE;
    }
  }

  RSA_free(rsa);
  free(err);
  BIO_free_all(keybio);
  return TRUE;
}

int main(int argc, char *argv)
{
  // 生成内存RSA密钥对
  char *private_key, *public_key;

  if (GenerateMemoryRSAKeys(&private_key, &public_key, 2048))
  {
    std::cout << "生成私钥: " << private_key << std::endl;
    std::cout << "生成公钥: " << public_key << std::endl;
  }

  char *encrypt, *decrypt;
  int encrypt_length;
  BOOL flag;

  // 公钥加密
  flag = rsa_encrypt(public_key, (char*)"hello lyshark", &encrypt, &encrypt_length, (char *)"public");
  if (flag == TRUE)
  {
    std::cout << "[公钥加密] 公钥加密字节: " << strlen(encrypt) << std::endl;
  }

  // 私钥解密
  flag = rsa_decrypt(private_key, encrypt, &decrypt, encrypt_length, (char *)"private");
  if (flag == TRUE)
  {
    std::cout << "[私钥解密] 私钥解密字节: " << decrypt << std::endl;
  }

  // 私钥加密
  flag = rsa_encrypt(private_key, (char*)"hello lyshark", &encrypt, &encrypt_length, (char*)"private");
  if (flag == TRUE)
  {
    std::cout << "[私钥加密] 私钥加密字节: " << strlen(encrypt) << std::endl;
  }

  // 公钥解密
  flag = rsa_decrypt(public_key, encrypt, &decrypt, encrypt_length, (char*)"public");
  if (flag == TRUE)
  {
    std::cout << "[公钥解密] 公钥解密字节: " << decrypt << std::endl;
  }

  system("pause");
  return 0;
}

读者可自行编译上述代码并运行,此时该代码将通过GenerateMemoryRSAKeys函数生成内存密钥对,并调用rsa_encryptrsa_decrypt两个函数实现对特定字符串的加解密功能,输出效果图如下;

20.6.2 公钥动态配对

有了上述内存生成RSA密钥对的方法,那么实现密钥对远程分发将变得很容易实现,首先我们来看客户端的实现方式,当客户端成功连接到了服务端则首先接收服务端传来的公钥,当收到服务器传来的公钥后通过使用rsa_encrypt函数并用公钥对待发送字符串进行加密,加密后调用send将加密数据发送给服务端,解密动作与加密保持一致,同样使用公钥进行解密,这段客户端代码如下所示;

int main(int argc, char* argv[])
{
  char buf[256] = "The National Aeronautics and Space Administration";

  WSADATA WSAData;

  // 初始化套接字库
  if (WSAStartup(MAKEWORD(2, 0), &WSAData))
  {
    return 0;
  }

  // 创建套接字
  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 public_key[1024] = { 0 };
    int recv_key_flag = recv(client_socket, public_key, 1024, 0);
    if (recv_key_flag > 0)
    {
      std::cout << "接收公钥字节: " << public_key << std::endl;
    }

    // 公钥加密并发送数据
    char* encrypt = nullptr;
    int encrypt_length = 0;

    rsa_encrypt(public_key, buf, &encrypt, &encrypt_length, (char*)"public");
    std::cout << "[服务端发送] 公钥加密字节: " << strlen(encrypt) << std::endl;
    send(client_socket, encrypt, encrypt_length, 0);

    // 公钥接收并解密数据
    char* decrypt = nullptr;

    memset(buf, 0, 256);
    recv(client_socket, buf, 256, 0);
    rsa_decrypt(public_key, buf, &decrypt, 256, (char *)"public");
    std::cout << "[服务端返回] 原始数据包: " << decrypt << std::endl;

    closesocket(client_socket);
    WSACleanup();
  }

  system("pause");
  return 0;
}

与客户端相比,服务端在执行时只是多出来了执行GenerateMemoryRSAKeys函数的功能,通过执行该函数我们可以得到一个动态的内存加密密钥对,有了密钥对则我们就可以使用私钥对数据进行加密与解密操作,如下是服务端核心实现代码;

int main(int argc, char* argv[])
{
  WSADATA WSAData;

  // 初始化套接字库
  if (WSAStartup(MAKEWORD(2, 0), &WSAData))
  {
    return 0;
  }

  // 创建套接字
  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);

  // 生成RSA密钥对
  char* private_key, *public_key;
  BOOL gen_flag = GenerateMemoryRSAKeys(&private_key, &public_key, 2048);
  if (gen_flag == TRUE)
  {
    // std::cout << "生成私钥: " << private_key << std::endl;
    // std::cout << "生成公钥: " << public_key << std::endl;
    std::cout << "[+] 已生成RSA密钥对" << std::endl;
  }

  // 接收请求
  SOCKET message_socket;
  if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) != INVALID_SOCKET)
  {
    // 发送公钥给客户端
    int put_key_flag = send(message_socket, public_key, strlen(public_key), 0);
    if (put_key_flag > 0)
    {
      std::cout << "本地私钥字节: " << private_key << std::endl;
      std::cout << "发送公钥字节: " << public_key << std::endl;
    }

    // 私钥解密: 接收并解密
    char recv_message[256] = { 0 };
    recv(message_socket, recv_message, 256, 0);

    char* decrypt = nullptr;
    rsa_decrypt(private_key, recv_message, &decrypt, 256, (char*)"private");
    std::cout << "[客户端返回] 原始数据包: " << decrypt << std::endl;

    // 私钥加密: 加密并发送
    char send_message[256] = "hello lyshark";
    char* encrypt = nullptr;
    int encrypt_length = 0;

    rsa_encrypt(private_key, send_message, &encrypt, &encrypt_length, (char*)"private");
    send(message_socket, encrypt, encrypt_length, 0);
  }
  closesocket(server_socket);
  WSACleanup();

  system("pause");
  return 0;
}

读者可自行编译并运行上述代码,首先运行服务端接着运行客户端,读者则可看到如下图所示的输出信息;

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

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

相关文章

开源检测数据库是否明文存储工具——OpenDLP的使用教程

前言 这些天Darren洋在做项目安全test调研的过程中&#xff0c;发现了一款非常不错的可自定义敏感数据类型的检测数据库是否明文存储工具。 OpenDLP 是一个开源项目&#xff0c;提供用于数据丢失预防&#xff08;DLP&#xff09;的工具。它允许自定义扫描规则&#xff0c;包括对…

一、Hadoop初始化配置(final+ubuntu保姆级教程)

1、配置虚拟机 三台虚拟机&#xff0c;分别为node1、node2、node3&#xff0c;内存分别为4G、2G、2G&#xff0c;现存最好为&#xff08;>40G&#xff09;&#xff0c;如下&#xff1a; 2、修改主机名 分别打开三台虚拟机&#xff0c;root用户输入一下命令&#xff1a; no…

openLayers--绘制多边形、获取视图的中心点、获取当前地图等级、设置地图等级

openLayers绘制多边形、获取视图中心点 前言效果图1、导入LineString2、创建添加多边形3、定义多变形样式4、获取当前视图的中心点5、获取当前视图等级6、设置地图等级 前言 上一篇文章在vue项目中绘制了openlayers绘制了地图和标记点&#xff0c;本篇文章讲解openlayers绘制多…

Java医院HIS系统源码

Java医院HIS系统源码 项目描述 该项目是用springbootlayuishiro写的医院管理系统&#xff0c;该系统的业务比较复杂&#xff0c;数据库一共有36张表。项目的视频业务参考文档&#xff0c;都在百度云盘中。可以先看看视频和参考文档。 运行环境 jdk8mysqlIntelliJ IDEAmaven…

Docker学习——③

文章目录 1、Docker Registry&#xff08;镜像仓库&#xff09;1.1 什么是 Docker Registry&#xff1f;1.2 镜像仓库分类1.3 镜像仓库工作机制1.4 常用的镜像仓库 2、镜像仓库命令3、镜像命令[部分]4、容器命令[部分]4.1 docker run4.2 docker ps 5、CentOS 搭建一个 nginx 服…

英伟达显卡深度学习训练微调环境安装清单

可以考虑 安装完操作系统后&#xff0c;安装更新及其他基础软件如gcc cmake&#xff0c; 再安装英伟达几件套&#xff08;这里列出了四个&#xff09; 如果自带的python版本在3.8或以上&#xff0c;再安装python常用库。 python版本不能太低&#xff0c;看你要跑的代码的需求了…

Leetcode-70 爬楼梯

递归公式方法会出现超时&#xff08;时间复杂度大于On^2&#xff09; 方法一&#xff1a;依然使用递归思想&#xff0c;由于递归过程中会出现反复计算问题&#xff0c;所以定义两个变量分别记录f(n-1)和f(n-2)的值&#xff0c;避免重复计算&#xff0c;减小时间复杂度 class …

mysql索引深度学习

索引是什么&#xff1f; 索引是一种用于加快查询和索引的数据结构&#xff0c;其本质上就是一种排序好的数据结构&#xff0c;就类似书的目录。 索引的底层有多种实现的结构&#xff1a;b树&#xff0c;b树&#xff0c;Hash&#xff0c;红黑树。InnoDB和MyISAM的索引都是通过…

【安全】Java幂等性校验解决重复点击(6种实现方式)

目录 一、简介1.1 什么是幂等&#xff1f;1.2 为什么需要幂等性&#xff1f;1.3 接口超时&#xff0c;应该如何处理&#xff1f;1.4 幂等性对系统的影响 二、Restful API 接口的幂等性三、实现方式3.1 数据库层面&#xff0c;主键/唯一索引冲突3.2 数据库层面&#xff0c;乐观锁…

Flink SQL 窗口聚合详解

1.滚动窗⼝&#xff08;TUMBLE&#xff09; **滚动窗⼝定义&#xff1a;**滚动窗⼝将每个元素指定给指定窗⼝⼤⼩的窗⼝&#xff0c;滚动窗⼝具有固定⼤⼩&#xff0c;且不重叠。 例如&#xff0c;指定⼀个⼤⼩为 5 分钟的滚动窗⼝&#xff0c;Flink 将每隔 5 分钟开启⼀个新…

Activiti7工作原理

Java Activiti是一个开源的工作流引擎&#xff0c;用于管理和执行业务流程。 它是基于BPMN 2.0标准的&#xff0c;提供了丰富的功能和灵活性。 Java Activiti的工作原理如下&#xff1a; 1.Java Activiti 流程建模 使用BPMN 2.0标准的图形化编辑器&#xff0c;可以创建和定义…

基于饥饿游戏算法的无人机航迹规划-附代码

基于饥饿游戏算法的无人机航迹规划 文章目录 基于饥饿游戏算法的无人机航迹规划1.饥饿游戏搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用饥饿游戏算法来优化无人机航迹规划。 …

Docker容器技术实战3

8、docker原生网络 Docker原生网络基于Linux桥接技术和虚拟网络接口&#xff0c;使用了Linux内核的网络功能。每个Docker容器都有自己的网络命名空间&#xff0c;这使得容器之间可以使用独立的IP地址&#xff0c;并隔离了容器的网络栈。 当创建一个Docker原生网络时&#xff…

Apache Flink 1.12.0 on Yarn(3.1.1) 所遇到的問題

Apache Flink 1.12.0 on Yarn(3.1.1) 所遇到的問題 新搭建的FLINK集群出现的问题汇总 1.新搭建的Flink集群和Hadoop集群无法正常启动Flink任务 查看这个提交任务的日志无法发现有用的错误信息。 进一步查看yarn日志&#xff1a; 发现只有JobManager的错误日志出现了如下的…

JOSEF约瑟 数显三相电压继电器 HJY-931A/D 导轨安装

名称&#xff1a;数字交流三相电压继电器型号&#xff1a;HJY-93系列品牌&#xff1a;JOSEF约瑟电压整定范围&#xff1a;10~450VAC额定电压&#xff1a;200、400VAC功率消耗&#xff1a;≤5W HJY系列 数字交流三相电压继电器 系列型号 HJY-931A/D数字式交流三相电压继电器&am…

吴恩达《机器学习》5-6:向量化

在深度学习和数值计算中&#xff0c;效率和性能是至关重要的。一个有效的方法是使用向量化技术&#xff0c;它可以显著提高计算速度&#xff0c;减少代码的复杂性。接下来将介绍向量化的概念以及如何在不同编程语言和工具中应用它&#xff0c;包括 Octave、MATLAB、Python、Num…

lvgl生成图片

网址&#xff1a;https://lvgl.io/tools/imageconverter CF_TRUE_COLOR:可以生成565&#xff0c;232&#xff0c;等多种形式的数组&#xff0c;选择Carray,会生成C数组。

2023.11.2事件纪念

然而造化又常常为庸人设计,以时间的流逝,来洗涤旧迹,仅以留下淡红的血色和微漠的悲哀。 回顾这次事件&#xff0c;最深的感触就是什么是团队的力量&#xff01; 当我们看到希望快要成功的时候&#xff0c;大家洋溢出兴奋开心的表情&#xff0c;一起的欢声笑语&#xff1b;但看…

抖音群控软件的作用是什么?

随着智能手机的普及和社交媒体的兴起&#xff0c;抖音成为了人们日常生活中不可或缺的一部分&#xff0c;许多人都通过抖音平台分享自己的生活、展示才艺、开展商业活动等。 然而&#xff0c;要想在抖音上获得更多的关注和收益&#xff0c;需要投入大量的时间和精力&#xff0…

【C语言初学者周冲刺计划】3.2将一个数组中的值逆序重新存放

目录 1解题思路&#xff1a; 2代码 3运行代码如图&#xff1a; 4总结&#xff1a; 1解题思路&#xff1a; 首先学会如何利用循环输入位数和输入数值&#xff0c;然后再利用循环逆序即可 2代码 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() { int…