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

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

20.6 OpenSSL 套接字分发RSA公钥的更多相关文章

  1. 4月20日 python学习总结 套接字工作流程

    一.套接字工作流程 一个生活中的场景.你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了.等交流结束,挂断电话结束此次交谈. 生活中的场景就解释了这 ...

  2. 网络编程初识和socket套接字

    网络的产生 不同机器上的程序要通信,才产生了网络:凡是涉及到倆个程序之间通讯的都需要用到网络 软件开发架构 软件开发架构的类型:应用类.web类 应用类:qq.微信.网盘.优酷这一类是属于需要安装的桌 ...

  3. 网络基础之网络协议篇---CS架构--网络通信--osi 协议---套接字socket--粘包

    1 C\S 客户端/服务器架构: .硬件 C/S架构 (打印机) .软件 C/S 架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务 ...

  4. 网络编程 套接字socket TCP UDP

    网络编程与套接字 网络编程 网络编程是什么: ​ 网络通常指的是计算机中的互联网,是由多台计算机通过网线或其他媒介相互链接组成的 ​ 编写基于网络的应用程序的过程序称之为网络编程. 网络编程最主要的工 ...

  5. ZeroMQ接口函数之 :zmq_socket – 创建ZMQ套接字

    ZeroMQ API 目录 :http://www.cnblogs.com/fengbohello/p/4230135.html ZeroMQ 官方地址:http://api.zeromq.org/4 ...

  6. linux原始套接字(3)-构造IP_TCP发送与接收

    一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...

  7. C语言与套接字

    我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...

  8. 《Unix网络编程》卷一(简介TCP/IP、基础套接字编程)

    通常说函数返回某个错误值,实际上是函数返回值为-1,而全局变量errno被置为指定的常值(即称函数返回这个错误值). exit终止进程,Unix在一个进程终止时总是关闭该进程所有打开的描述符. TCP ...

  9. 基本套接字编程(5) -- epoll篇

    1. epoll技术 epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃 ...

  10. 基本套接字编程(4) -- poll篇

    1. poll技术 poll函数起源于SVR3,最初局限于流设备.SVR4取消了这种限制,允许poll工作在任何描述符上.poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息 ...

随机推荐

  1. vue 状态管理 五、Module用法

    系列导航 vue 状态管理 一.状态管理概念和基本结构 vue 状态管理 二.状态管理的基本使用 vue 状态管理 三.Mutations和Getters用法 vue 状态管理 四.Action用法 ...

  2. 如何将接口的返回值中所需信息提取出来作为其他接口的入参使用(postman与jmeter的使用)

    一.背景: 偶尔会用到一个场景,两个接口之前的调用有依赖关系,将其中一个的返回参数中的部分信息取出来作为入参在第二个接口中使用,代码内是比较好实现,只要定义一个变量,用于参数传递. 如果是测试过程中使 ...

  3. java进阶(28)--Map集合

    一.Map简介: 1.Map与collection没有继承关系 2.Map集合以key与value的方式存储数据   二.常用方法: 1.void clear():清空Map集合

  4. LLM面面观之Prefix LM vs Causal LM

    1. 背景 关于Prefix LM和Causal LM的区别,本qiang在网上逛了一翻,发现多数客官只给出了结论,但对于懵懵的本qiang,结果仍是懵懵... 因此,消遣了多半天,从原理及出处,交出 ...

  5. SV概述

    System Verilog概述 路科验证视频,B站可看(补充一下知识) 学习SV之前,最好有Verilog基础 SV诞生 SV发展历史 Verilog - 偏向于设计 System Verilog ...

  6. STM32 芯片锁死解决方法

    芯片锁死原因: 1.烧进去的工程对应器件与目标器件不一致: 2.烧进去的工程HSE_VALUE与目标板上晶振频率不一致: 3.... 解决方法: 1.工程设置 2.按住复位按键,或短接复位脚电容,点击 ...

  7. JavaScript - input 上传图片 并展示 (食用简单)

    <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF ...

  8. 如何让pc端网站在手机上可以等比缩放的整个显示

      将 头部标签的  <meta name="viewport" content="width=device-width, initial-scale=1.0&qu ...

  9. C++ 关键字 new

    new new 是C++ 中的关键字,有两个含义 new 表达式 作为运算符的函数名,也就是 operator new new 表达式 提供一个特定的内存分配格式,返回在存储空间上构造的对象或对象数组 ...

  10. linux环境C语言实现:h264与pcm封装成AVI格式

    ​ 前言 拖了很久的AVI音视频封装实例,花了一天时间终于调完了,兼容性不是太好,但作为参考学习使用应该没有问题. RIFF和AVI以及WAV格式,可以参考前面的一些文章.这里详细介绍将一个H264视 ...