20.6 OpenSSL 套接字分发RSA公钥

通过上一节的学习读者应该能够更好的理解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;
}

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

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

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

相关文章

YOLO目标检测——路标检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;路标检测数据集在自动驾驶、交通安全监控、导航系统、城市规划和车辆行为分析等领域都有广泛应用的潜力数据集说明&#xff1a;路标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含有停止标志、速度限制标志、…

四维轻云如何实现地理空间数据在线管理、编辑及分享?

四维轻云是一款轻量化的地理空间数据网页管理平台&#xff0c;支持多种地理空间数据的在线管理、编辑及分享。现阶段&#xff0c;平台具有项目管理、数据上传、场景搭建、发布分享、团队成员、素材库等功能模块&#xff0c;支持项目团队成员在线协作管理&#xff0c;能够在线管…

运用vioovi视与视标准工时工具,实现精益生产

在制造业领域&#xff0c;标准工时的测量和管理对于提高生产效率和降低成本至关重要。然而&#xff0c;传统的标准工时方法在面对日益增长的各种成本时显得力不从心。为了解决这一问题&#xff0c;企业需要采用一种更科学、更高效的方法来管理和优化生产流程。vioovi的视与视标…

Flink源码解析八之任务调度和负载均衡

源码概览 jobmanager scheduler:这部分与 Flink 的任务调度有关。 CoLocationConstraint:这是一个约束类,用于确保某些算子的不同子任务在同一个 TaskManager 上运行。这通常用于状态共享或算子链的情况。CoLocationGroup & CoLocationGroupImpl:这些与 CoLocationCon…

LangChain+LLM实战---Midjourney(v5.1) Prompt深度剖析

原文&#xff1a;Anatomy of Midjourney Promps: In-Depth Study for effective Prompting Strategies — V5.1 examples 作者&#xff1a;Michael King 你是否曾经发现自己盯着Midjourney的空白画布&#xff0c;手指悬停在键盘上&#xff0c;让我问自己&#xff1a;“我应该…

软件测试/测试开发丨利用ChatGPT 生成自动化测试脚本

点此获取更多相关资料 简介 自动化测试脚本可以模拟用户与应用程序的交互&#xff0c;例如点击按钮、输入数据、导航到不同的页面等等&#xff0c;以验证应用程序的正确性、性能和稳定性。 自动化测试在回归测试、冒烟测试等测试流程中都可以极大地起到节省时间、节省人力的作…

JavaScript_Date对象_实例方法_set类

设置一年后的今天&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document</…

《golang设计模式》第三部分·行为型模式-04-迭代器模式(Iterator)

文章目录 1. 概念1.1 角色1.2 类图 2. 代码示例2.1 需求2.2 代码2.3 类图 1. 概念 迭代器&#xff08;Iterator&#xff09;能够在不暴露聚合体内部表示的情况下&#xff0c;向客户端提供遍历聚合元素的方法。 1.1 角色 InterfaceAggregate&#xff08;抽象聚合&#xff09;…

瓦斯抽采VR应急救援模拟仿真系统筑牢企业安全生产防线

矿工素质对安全生产的影响很大。传统的煤矿安全事故培训出于条件差、经验少加上侥幸心理&#xff0c;导致其在教学内容时过于简单且不切合实际&#xff0c;无法真正发挥培训作用。瓦斯检查作业VR模拟实操培训通过真实还原煤矿作业环境&#xff0c;让受训者身临其境地进入三维仿…

发送Http请求的HttpClientUtil工具

发送Http请求的HttpClientUtil工具 代码如下&#xff1a; /*** author xuan* create 2023/11/6*/ public class HttpUtil {// 创建连接池管理器private static final PoolingHttpClientConnectionManager connMgr new PoolingHttpClientConnectionManager();// http客户端pr…

Java--类和对象

目录 面向对象一.类1.类的创建默认初始化2.类的实例化3.注意事项利用类的创建来交换值 二.this1.使用this2.可使用this来调用其他构造方法来简化 三.构造方法3.1概念3.2特性3.3不带参数的构造方法3.4带参数的构造方法当使用自定义的构造方法后&#xff0c;再删除时&#xff0c;…

【Orangepi Zero2 全志H616】驱动舵机控制 / Linux定时器(signal、setitimer)

一、SG90舵机开发 舵机基本介绍 二、Linux定时器 signal 函数setitimer 函数原型signal、setitimer函数API调用 三、舵机 软件PWM实现 一、SG90舵机开发 舵机基本介绍 如下图所示&#xff0c;最便宜的舵机sg90&#xff0c;常用三根或者四根接线&#xff0c;黄色为PWM信号控…

JMeter:断言之响应断言

一、断言的定义 断言用于验证取样器请求或对应的响应数据是否返回了期望的结果。可以是看成验证测试是否预期的方法。 对于接口测试来说&#xff0c;就是测试Request/Response&#xff0c;断言即可以针对Request进行&#xff0c;也可以针对Response进行。但大部分是对Respons…

【WinForm详细教程五】WinForm中的MenuStrip 、ContextMenuStrip 、ToolStrip、StatusStrip控件

文章目录 1.MenuStrip2.ContextMenuStrip3.ToolStrip4.StatusStrip 1.MenuStrip MenuStrip作为一个容器可以包含多个菜单项。MenuStrip 的重要属性包括&#xff1a; Name&#xff1a;菜单的名字Dock&#xff1a;菜单的停靠位置Items&#xff1a;菜单项的集合 ToolStripMenuI…

多种循环法打印乘法表

1 问题 使用多种循环法打印乘法表&#xff0c;有助于巩固夯实循环的语法及用法。 使用for-for、for-while、while-for方法实现乘法表。 2 方法 &#xff08;1&#xff09;for-for:使用两个for.. in..来实现乘法表。 &#xff08;2&#xff09;for-while:使用一个for语句再一个w…

Visual Studio Code将中文写入变量时,中文老是乱码问题

对于这个问题&#xff0c;我也是弄了很久才知道&#xff0c;编码格式的问题 在此之前我们要先下载个插件 照这以上步骤&#xff0c;最后按F6运行即可&#xff0c;按F6是利用我们刚刚下载的插件进行编译&#xff0c;唯一有一点不好就是&#xff0c;用这种插件运行的话&#xff…

【算法| 差分 No.1】AcWing 797. 差分 AcWing 798. 差分矩阵

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望对大家有所帮…

VMware Ubuntu 共享文件夹

VMware Ubuntu 共享文件夹 flyfish 物理机配置 Network Adapter设置 此处设置为NAT Shared Folders设置 虚拟机配置 vmware-hgfsclient sudo vmhgfs-fuse .host:/ /mnt -o nonempty -o allow_other 或者 sudo vmhgfs-fuse .host:/ /mnt/ -o allow_other第一行命令是查看共…

如何保障企业TikTok直播网络的可靠性?

在这个直播流量爆炸的时代&#xff0c;直播带货的营销方式逐渐走向大众&#xff0c;企业通过直播的方式向消费者展示产品和服务&#xff0c;从而提高品牌知名度。而TikTok作为全球最受欢迎的短视频和直播社交平台&#xff0c;拥有庞大且活跃的用户群体&#xff0c;是不少企业直…

每日一题 --- 力扣318----最大单词长度乘积

这道题时间复杂度我感觉设置的不是很好&#xff0c;应该最好是有一个1000变成10000就行。 因为我在做这道题的时候被误导了&#xff0c;以为双重循环暴力判断一下也能过&#xff0c;因为1000*1000 *26的时间复杂度没有到1亿&#xff0c;那么我刚开始认为是能过的&#xff0c;结…