14.11 Socket 基于时间加密通信
在之前的代码中我们并没有对套接字进行加密,在未加密状态下我们所有的通信内容都是明文传输的,这种方式在学习时可以使用但在真正的开发环境中必须要对数据包进行加密,此处笔者将演示一种基于时间的加密方法,该加密方法的优势是数据包每次发送均不一致,但数据包内的内容是一致的,当抓包后会发现每次传输的数据包密文是随机变化的,但内容始终保持一致,也就是说两个拥有相同内容的数据被加密后,数据包密文不同,其主要运用了基于当前时间戳的通信机制。
14.11.1 实现加盐函数
加盐函数此处笔者采用基于时间的加盐方式,取出用户分钟数与秒数并生成随机数作为盐,通过三者的混合计算出一串解密密钥对,此方法的必须保证服务端与客户端时间同步,如果不同步则无法计算出正确的密钥对,解密也就无法继续了。
代码中函数GenRandomString用于实现生成一个随机数,该函数接受一个随机数长度并返回一个字符串。接着GetPasswordSalt_OnSec与GetPasswordSalt_OnMin函数分别用于根据当前秒与分钟生成一个随机的盐,函数GetXorKey则用于对特定一段字符串进行异或处理并生成一个Key,函数CRC32则用于对字符串计算得到一个哈希值。
#include <WinSock2.h>
#include <Windows.h>
#include <iostream>
#include <random>
#include <time.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
typedef struct
{
char random[1024];
char Buffer[4096];
}SocketPackage;
// 产生长度为length的随机字符串
char* GenRandomString(int length)
{
int flag, i;
char* string;
srand((unsigned)time(NULL));
if ((string = (char*)malloc(length)) == NULL)
{
return NULL;
}
for (i = 0; i < length - 1; i++)
{
flag = rand() % 3;
switch (flag)
{
case 0:
string[i] = 'A' + rand() % 26;
break;
case 1:
string[i] = 'a' + rand() % 26;
break;
case 2:
string[i] = '0' + rand() % 10;
break;
default:
string[i] = 'x';
break;
}
}
string[length - 1] = '\0';
return string;
}
// 通过秒数生成盐
int GetPasswordSalt_OnSec()
{
time_t nowtime;
struct tm* p;;
time(&nowtime);
p = localtime(&nowtime);
if (p->tm_sec <= 10)
return 2;
else if (p->tm_sec > 10 && p->tm_sec <= 20)
return 5;
else if (p->tm_sec > 20 && p->tm_sec <= 30)
return 8;
else if (p->tm_sec > 30 && p->tm_sec <= 40)
return 4;
else if (p->tm_sec > 40 && p->tm_sec <= 50)
return 9;
else
return 3;
}
// 通过分钟生成盐
int GetPasswordSalt_OnMin()
{
time_t nowtime;
struct tm* p;;
time(&nowtime);
p = localtime(&nowtime);
return p->tm_min;
}
// 获取异或整数
long GetXorKey(const char* StrPasswd)
{
char cCode[32] = { 0 };
strcpy(cCode, StrPasswd);
DWORD Xor_Key = 0;
for (unsigned int x = 0; x < strlen(cCode); x++)
{
Xor_Key = Xor_Key + (GetPasswordSalt_OnSec() * GetPasswordSalt_OnMin()) + cCode[x];
}
return Xor_Key;
}
// 计算CRC32校验和
DWORD CRC32(char* ptr, DWORD Size)
{
DWORD crcTable[256], crcTmp1;
// 动态生成CRC-32表
for (int i = 0; i < 256; i++)
{
crcTmp1 = i;
for (int j = 8; j > 0; j--)
{
if (crcTmp1 & 1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
else crcTmp1 >>= 1;
}
crcTable[i] = crcTmp1;
}
// 计算CRC32值
DWORD crcTmp2 = 0xFFFFFFFF;
while (Size--)
{
crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF];
ptr++;
}
return (crcTmp2 ^ 0xFFFFFFFF);
}
int main(int argc, char *argv[])
{
// 生成一个随机数作为盐
char* uuid = GenRandomString(7);
std::cout << "随机数: " << uuid << std::endl;
int sec_key = GetPasswordSalt_OnSec();
std::cout << "根据秒数生成盐: " << sec_key << std::endl;
int min_key = GetPasswordSalt_OnMin();
std::cout << "根据分钟生成盐: " << min_key << std::endl;
// 传入随机数作为密钥对,生成最终密钥
long key = GetXorKey(uuid);
std::cout << "最终密钥: " << key << std::endl;
int crc32 = CRC32(uuid, 10);
std::cout << "crc32: " << hex << crc32 << std::endl;
system("pause");
return 0;
}
14.11.2 实现加密函数
对于加密函数SendEncryptionPage的实现流程,首先在发送数据包之前调用GenRandomString()生成一个7位的随机数,并将随机数拷贝到pack.random结构内,接着调用异或函数GetXorKey(uuid)生成加密密钥,并依次循环对pack.Buffer中的数据进行逐字节加密。最后将加密数据包发送出去,并接着计算该数据包的CRC32值,并再次通过send()函数将其发送给客户端。
// 加密数据包并发送
bool SendEncryptionPage(SOCKET* ptr, char* send_data)
{
char buf[8192] = { 0 };
SocketPackage pack;
memset(buf, 0, 8192);
// 生成随机数并拷贝到结构体
char* uuid = GenRandomString(7);
strcpy(pack.random, uuid);
std::cout << "[客户端] 本次随机密钥对: " << uuid << std::endl;
// 生成并拷贝加密数据
strcpy(pack.Buffer, send_data);
int key = GetXorKey(uuid);
std::cout << " --> 生成随机 key = " << key << std::endl;
for (int x = 0; x < strlen(pack.Buffer); x++)
{
pack.Buffer[x] = pack.Buffer[x] ^ key;
}
// 加密数据包并发送
memcpy(buf, &pack, sizeof(SocketPackage));
send(*ptr, buf, sizeof(buf), 0);
// 计算CRC32校验和,并发送给服务端
DWORD crc32 = CRC32(buf, 100);
char send_crc32[1024] = { 0 };
sprintf(send_crc32, "%x", crc32);
std::cout << " --> 发送CRC32校验和 = " << send_crc32 << std::endl;
// 发送CRC32计算结果
send(*ptr, send_crc32, sizeof(send_crc32), 0);
return true;
}
14.11.3 实现解密函数
解密函数RecvDecryptPage的实现流程与加密函数需要对应,首先当收到加密后的数据包时,该数据包会被存入buf变量内存储,并强制类型转为结构体。接着调用GetXorKey函数生成随机数,该随机数是通过本机时间通过分钟与秒数生成的盐,并与用户密码进行异或得到。通过接收服务器端发过来的CRC32校验码,比对原始数据包有没有被修改过,该校验码是服务端通过数据包生成的,最后客户端计算收到的数据包CRC32是否与服务端一致,一致则继续执行异或循环对数据包进行逐字节解包。
// 接收数据包并解密
char* RecvDecryptPage(SOCKET *ptr)
{
char buf[8192] = { 0 };
// 接收加密后的数据包
memset(buf, 0, sizeof(buf));
recv(*ptr, buf, sizeof(buf), 0);
SocketPackage* pack = (SocketPackage*)buf;
// 接收随机数并获取异或密钥
int key = GetXorKey(pack->random);
std::cout << "[服务端] 基于时间计算 key = " << key << std::endl;
// 服务端验证网络CRC32数据包是否一致
char recv_crc32[1024] = { 0 };
recv(*ptr, recv_crc32, sizeof(recv_crc32), 0);
std::cout << " --> 收到客户端CRC32校验和 = " << recv_crc32 << std::endl;
// 计算CRC32是否与发送值一致
DWORD crc32 = CRC32(buf, 100);
char this_crc32[1024] = { 0 };
sprintf(this_crc32, "%x", crc32);
std::cout << " --> 计算本地数据包CRC32校验和 = " << this_crc32 << std::endl;
if (strcmp(recv_crc32, this_crc32) == 0)
{
std::cout << " --> 校验和一致" << std::endl;
// 开始解密数据包
for (int x = 0; x < strlen(pack->Buffer); x++)
{
pack->Buffer[x] = pack->Buffer[x] ^ key;
}
std::cout << " --> 解密后的数据: " << pack->Buffer << std::endl;
std::cout << std::endl;
return pack->Buffer;
}
}
14.11.4 数据加密收发
当有了上述完整加解密函数的封装之后读者就可以通过使用套接字的方法来实现数据包的通信,当需要接收数据时可以直接调用RecvDecryptPage()函数并传入当前活动套接字,而如果需要发送数据则也只需要调用SendEncryptionPage()函数即可,由于函数已被封装所以在传输数据时与普通套接字函数的使用没有任何区别。
针对服务端的主函数如下所示;
int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock, msgsock;
struct sockaddr_in ServerAddr;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) != SOCKET_ERROR)
{
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(9999);
ServerAddr.sin_addr.s_addr = INADDR_ANY;
sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));
listen(sock, 10);
}
while (1)
{
msgsock = accept(sock, (LPSOCKADDR)0, (int*)0);
// 接收数据并解密
char * recv_data = RecvDecryptPage(&msgsock);
std::cout << "获取包内数据: " << recv_data << std::endl;
// 发送数据
SendEncryptionPage(&msgsock, (char*)"ok");
std::cout << std::endl;
closesocket(msgsock);
}
closesocket(sock);
WSACleanup();
return 0;
}
针对客户端的主函数如下所示;
int main(int argc, char* argv[])
{
while (1)
{
WSADATA WSAData;
SOCKET sock;
struct sockaddr_in ClientAddr;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) != SOCKET_ERROR)
{
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = htons(9999);
ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sock = socket(AF_INET, SOCK_STREAM, 0);
int Ret = connect(sock, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr));
if (Ret == 0)
{
// 发送数据
char send_message[4096] = "hello lyshark";
SendEncryptionPage(&sock, send_message);
// 接收数据
char* recv_data = RecvDecryptPage(&sock);
std::cout << "接收数据包: " << recv_data << std::endl;
std::cout << std::endl;
}
}
closesocket(sock);
WSACleanup();
Sleep(5000);
}
return 0;
}
读者可自行将上述代码片段组合起来,并分别运行服务端与客户端,当运行后读者可看到如下图所示的输出信息;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/f1f85090.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
14.11 Socket 基于时间加密通信的更多相关文章
- Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信
Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信 背景 上一讲我们介绍了网络编程的一些概念.socket的网络编程的有关概念 这一讲我们来看UDP 通信. 知识 U ...
- Python基于socket模块实现UDP通信功能示例
Python基于socket模块实现UDP通信功能示例 本文实例讲述了Python基于socket模块实现UDP通信功能.分享给大家供大家参考,具体如下: 一 代码 1.接收端 import ...
- Linux 系统编程 学习:008-基于socket的网络编程3:基于 TCP 的通信
背景 上一讲我们介绍了 基于UDP 的通信 这一讲我们来看 TCP 通信. 知识 TCP(Transmission Control Protoco 传输控制协议). TCP是一种面向广域网的通信协议, ...
- 开源项目SMSS发开指南(四)——SSL/TLS加密通信详解
本文将详细介绍如何在Java端.C++端和NodeJs端实现基于SSL/TLS的加密通信,重点分析Java端利用SocketChannel和SSLEngine从握手到数据发送/接收的完整过程.本文也涵 ...
- Python的网络编程[0] -> socket[2] -> 利用 socket 建立 TCP/UDP 通信
Socket 目录 socket 的 TCP/IP 通信基本建立过程 socket 的 UDP 通信基本建立过程 socket 的 UDP 广播式通信基本建立过程 socket 的多线程通信建立过程 ...
- 开源项目SMSS发开指南(五)——SSL/TLS加密通信详解(下)
继上一篇介绍如何在多种语言之间使用SSL加密通信,今天我们关注Java端的证书创建以及支持SSL的NioSocket服务端开发.完整源码 一.创建keystore文件 网上大多数是通过jdk命令创建秘 ...
- Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级
1.权限管理 权限管理的基本定义:百度百科. 基于<Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员.后台管理员同时登录>我们做过了登录认证, ...
- SSL及其加密通信过程
SSL及其加密通信过程 什么是SSL SSL英文全称Secure Socket Layer,安全套接层,是一种为网络通信提供安全以及数据完整性的安全协议,它在传输层对网络进行加密.它主要是分为两层: ...
- Oracle之表空间基于时间点的恢复
记一次优化过程中:一次误操作,在不影响其他表空间的情况下:采用表空间基于时间点的恢复(TSPITR)方法恢复数据的过程. 1.TSPITR恢复原理 TSPITR目前最方便的方法是使用RMAN进行 ...
- 深度学习与计算机视觉(11)_基于deep learning的快速图像检索系统
深度学习与计算机视觉(11)_基于deep learning的快速图像检索系统 作者:寒小阳 时间:2016年3月. 出处:http://blog.csdn.net/han_xiaoyang/arti ...
随机推荐
- MYSQL之批量删除(mybatis)
如果参数是array数组 <update id="deleteAll"> delete from C_V WHERE UUID in <foreach item= ...
- 行行AI人才直播第7期:奇计AI创始人左晟《AI时代的商业挑战和机遇》
行行AI人才是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台,是园子商业化努力的一个重要方向. 行行AI人才直播希望以直播的方式让大家更多了解AI行业的现状与未来可能的发展方向. 随着人工智 ...
- C++之函数的分文件编写
从黑马程序员的c++课里学到的函数的分文件编写 函数的分文件编写 作用:让代码结构更加清晰 函数分文件编写一般有4个步骤 1,创建后缀名为.h的头文件 2,创建后缀名为.cpp的源文件 3,在头文件中 ...
- minipc安装与设置Ubuntu
此文章是对刚刚在某宝买的minipc进行的Ubuntu server安装,以及部分应用过程 安装Ubuntu server22 参考一文搞懂Ubuntu Server 22.04.2安装 问题记录 开 ...
- Linux中的进程页表
是什么 进程页表是用于管理进程虚拟地址空间和物理内存之间映射关系的数据结构.它记录了进程中每个虚拟页对应的物理页的信息. 什么作用 进程使用进程页表的方式是通过虚拟地址访问内存.当进程访问一个虚拟地址 ...
- linux 软件包:UnixBench 性能测试工具、跑分神器
目录 安装 使用 结果示例 测试项说明 UnixBench是一个类unix系(Unix,BSD,Linux)统下的性能测试工具,一个开源工具,被广泛用与测试linux系统主机的性能.Unixbench ...
- 基于AIidlux平台的自动驾驶环境感知与智能预警
自动驾驶汽车又称为无人驾驶车,是一种需要驾驶员辅助或者完全不需操控的车辆. 自动驾驶分级: 自动驾驶系统的组成部分: 环境感知系统: 自动驾驶系统架构: 自动驾驶数据集: Aidlux的作用: YOL ...
- C++ 核心指南之 C++ 哲学/基本理念(下)
C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...
- Ehcache的Maven依赖及其配置文件
Ehcache的Maven依赖 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> ...
- P1113 杂务 (DAG拓扑排序--DP)
这是一道拓扑排序的模板题 0 额. 所需的前置知识: 图论相关的基本概念 建图,存图 图的遍历 非常入门的DP 下面进入正文 1 引入 拓扑排序是一类用于处理 DAG(Directed acyclic ...