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_encrypt与rsa_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公钥的更多相关文章
- 4月20日 python学习总结 套接字工作流程
一.套接字工作流程 一个生活中的场景.你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了.等交流结束,挂断电话结束此次交谈. 生活中的场景就解释了这 ...
- 网络编程初识和socket套接字
网络的产生 不同机器上的程序要通信,才产生了网络:凡是涉及到倆个程序之间通讯的都需要用到网络 软件开发架构 软件开发架构的类型:应用类.web类 应用类:qq.微信.网盘.优酷这一类是属于需要安装的桌 ...
- 网络基础之网络协议篇---CS架构--网络通信--osi 协议---套接字socket--粘包
1 C\S 客户端/服务器架构: .硬件 C/S架构 (打印机) .软件 C/S 架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务 ...
- 网络编程 套接字socket TCP UDP
网络编程与套接字 网络编程 网络编程是什么: 网络通常指的是计算机中的互联网,是由多台计算机通过网线或其他媒介相互链接组成的 编写基于网络的应用程序的过程序称之为网络编程. 网络编程最主要的工 ...
- ZeroMQ接口函数之 :zmq_socket – 创建ZMQ套接字
ZeroMQ API 目录 :http://www.cnblogs.com/fengbohello/p/4230135.html ZeroMQ 官方地址:http://api.zeromq.org/4 ...
- linux原始套接字(3)-构造IP_TCP发送与接收
一.概述 tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...
- C语言与套接字
我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...
- 《Unix网络编程》卷一(简介TCP/IP、基础套接字编程)
通常说函数返回某个错误值,实际上是函数返回值为-1,而全局变量errno被置为指定的常值(即称函数返回这个错误值). exit终止进程,Unix在一个进程终止时总是关闭该进程所有打开的描述符. TCP ...
- 基本套接字编程(5) -- epoll篇
1. epoll技术 epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃 ...
- 基本套接字编程(4) -- poll篇
1. poll技术 poll函数起源于SVR3,最初局限于流设备.SVR4取消了这种限制,允许poll工作在任何描述符上.poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息 ...
随机推荐
- Java23种设计模式学习笔记
创建型模式:关注对象的创建过程 1.单例模式: 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点 主要: 饿汉式(线程安全,调用效率高,但是不能延时加载) 懒汉式(线程安全,调用效率不高, ...
- VueTreeselect
https://www.vue-treeselect.cn/ 官网简介
- 报错:for..in loops iterate over the entire prototype chain, which is virtually never what you want.
for..in loops iterate over the entire prototype chain, which is virtually never what you want. 意思是使用 ...
- vue中引入其他网站页面
https://blog.csdn.net/qq_36706878/article/details/102593309
- Guava缓存工具类封装和使用
本文为博主原创,未经允许不得转载: Guava是谷歌提供的一款强大的java工具库,里面包含了很多方便且高效的工具,在项目开发中有业务场景需要保存数据到内存当中, 且只需要保存固定时间就可以,该数据只 ...
- .NET静态代码织入——肉夹馍(Rougamo)发布2.2
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...
- 【解决方案】如何使用 Http API 代替 OpenFeign 进行远程服务调用
目录 前言 一.何为OpenFeign 1.1@FeignClient注解 1.2注意事项 二.常见的Http API 2.1Apache 2.2Okhttp 2.3Hutool 三.RestTemp ...
- 21-CMOS门电路的逻辑式
CMOS门电路的逻辑式 通过CMOS门电路,写出门电路的表达式. 方法 只看下方,因为电路上下是对称的: 先找L(输出)的非,找的输出到地的通路,以原变量进行书写.最后将表达式取非,即可得到L.这种方 ...
- 使用VS开发人员工具观察类在内存中的布局
1.先要生成相应文件 2.打开VS2019开发人员工具 3.cd至文件目录 4.输入cl /d1 reportSingleClassLayoutanimal demo.cpp 其中reportSing ...
- UEditor 添加在线管理图片删除功能 (转载)
第一,需要添加一个 php 文件来实现删除功能,文件添加到: ueditor\php\action_delete.php 代码内容: <?php /*---------------------- ...