通过上一节的学习读者应该能够更好的理解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. Leaflet 使用图片作为地图

    Leaflet 使用图片作为地图 关键代码: L.CRS.Simple.transformation = new L.Transformation(1, 0, 1, 0); // 坐标原点切换为左上角 ...

  2. POJ 1015 Jury Compromise (完全背包)

    题目大意: 在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定.陪审团是由法官从公众中挑选的.先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团.选m 人的办法是:控方和辩 ...

  3. AtCoder Beginner Contest 210 (A~E)

    比赛链接:Here A - Cabbages 略 B - Bouzu Mekuri 略 C - Colorful Candies 用map维护连续一段区间的不同元素即可. int main() { c ...

  4. Codeforces Round #721 (Div. 2) AB思维,B2博弈,C题map

    补题链接:Here 1527A. And Then There Were K 题目大意: 给一个正整数n,求最大的k,使得 \(n \& (n−1) \& (n−2) \& ( ...

  5. jdk(jvm)调式工具

    JConsole 可视化工具介绍一. JConsole介绍1.1 JConsole描述Jconsole (Java Monitoring and Management Console),一种基于JMX ...

  6. 【调试】netconsole的使用

    开发环境 客户端 开发板:FireFly-RK3399 Linux 4.4 IP:192.168.137.110 服务端 VMware Workstation Pro16,ubuntu 18.04 I ...

  7. P2058

    这道不难的题引发了我不少思考 我第一个版本是用vector嵌套vector写成的,后来发现没必要还存储那些已经超过24h的船,完全可以删除前面的船,因此把外层vector换成了deque. 即用deq ...

  8. Spring AOP原来是这样实现的

    Spring AOP 技术实现原理 在Spring框架中,AOP(面向切面编程)是通过代理模式和反射机制来实现的.本文将详细介绍Spring AOP的技术实现原理,包括JDK动态代理和CGLIB代理的 ...

  9. Nacos源码 (7) Nacos与Spring

    SpringCloud工程可以使用Nacos作为注册中心和配置中心,配置和使用非常简单,本文将简单介绍使用方式,并分析其实现方式. SpringCloud工程集成Nacos SpringCloud工程 ...

  10. 【C/C++】函数入参检查

    // 统计变参数量 #define CALC_VA_COUNT(arg...) \ ({ \ int count = 0; \ int insideQuotes = 0; \ const char * ...