14.11 Socket 基于时间加密通信

news2025/1/21 18:41:50

在之前的代码中我们并没有对套接字进行加密,在未加密状态下我们所有的通信内容都是明文传输的,这种方式在学习时可以使用但在真正的开发环境中必须要对数据包进行加密,此处笔者将演示一种基于时间的加密方法,该加密方法的优势是数据包每次发送均不一致,但数据包内的内容是一致的,当抓包后会发现每次传输的数据包密文是随机变化的,但内容始终保持一致,也就是说两个拥有相同内容的数据被加密后,数据包密文不同,其主要运用了基于当前时间戳的通信机制。

14.11.1 实现加盐函数

加盐函数此处笔者采用基于时间的加盐方式,取出用户分钟数与秒数并生成随机数作为盐,通过三者的混合计算出一串解密密钥对,此方法的必须保证服务端与客户端时间同步,如果不同步则无法计算出正确的密钥对,解密也就无法继续了。

代码中函数GenRandomString用于实现生成一个随机数,该函数接受一个随机数长度并返回一个字符串。接着GetPasswordSalt_OnSecGetPasswordSalt_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 许可协议。转载请注明出处!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序获取手机号(2023年10月 python版)[无需订阅]

技术栈&#xff1a; 1. 微信开发者工具中的调试基础库版本&#xff1a;3.1.2。 2. 后台&#xff1a;django。 步骤&#xff1a; 1. 首先在后台django项目的定时任务中增加一个下载access_token函数&#xff0c;并把得到的access_token保存在数据库中&#xff08;其实随便哪里…

SpringCloud学习笔记-gateway网关自定义全局过滤器

需求&#xff1a;定义全局过滤器&#xff0c;拦截请求&#xff0c;判断请求的参数是否满足下面条件&#xff1a; 参数中是否有authorization&#xff0c; authorization参数值是否为admin 如果同时满足则放行&#xff0c;否则拦截 实现&#xff1a; 在gateway中定义一个过…

C语言实现通讯录(超详细)

1.实现怎样一个通讯录 实现一个通讯录联系人信息&#xff1a;1.可以保存100个人的信息名字2.添加联系人年龄3.删除指定联系人性别4.查找指定联系人电话5.修改指定联系人住址6.排序联系人7.显示所有联系人信息 2.通讯录的实现 2.1创建两个源文件和一个头文件 首先我们创建con…

C++DAY47

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QLabel> #include <QLineEdit> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public…

数据结构----算法--五大基本算法(这里没有写分支限界法)和银行家算法

数据结构----算法–五大基本算法&#xff08;这里没有写分支限界法&#xff09;和银行家算法 一.贪心算法 1.什么是贪心算法 在有多个选择的时候不考虑长远的情况&#xff0c;只考虑眼前的这一步&#xff0c;在眼前这一步选择当前的最好的方案 二.分治法 1.分治的概念 分…

6-8 舞伴问题 分数 15

void DancePartner(DataType dancer[], int num) {LinkQueue maleQueue SetNullQueue_Link();LinkQueue femaleQueue SetNullQueue_Link();// 将男士和女士的信息分别加入对应的队列for (int i 0; i < num; i) {if (dancer[i].sex M){EnQueue_link(maleQueue, dancer[i]…

Vue跨域配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 配置详情 请在项目的vue.config.js中通过proxy的配置&#xff0c;解决Vue跨域&#xff1b;代码如下&#xff1a; const { defineConfig } require(vue/cli-service) modu…

用JavaScript输出0-9的两种方法、以及setTimeout的三个参数的意义

方法一&#xff1a; for (let i 0; i < 10; i) {setTimeout(() > {console.log(i);}, 1000) } 方法二&#xff1a;利用 setTimeout 函数的第三个参数&#xff0c;会作为回调函数的第一个参数传入 for (var i 0; i < 10; i) {setTimeout(i > {console.log(i);…

canvas画布中beginPath和closePath的作用要点

1.画笔画完一个图案后&#xff0c;立即又回到了原点&#xff0c;所以下次如果没有beginPath&#xff0c;就会从原点重新开始 2.beginPath相当于让画笔从原点抬起&#xff0c;重新开始一个新路径&#xff0c;不重复走之前的路径&#xff0c;所以不会覆盖之前的路径 3.closePat…

通过IP地址查询避免电子招投标串标的方法

随着电子招投标的广泛应用&#xff0c;诚实和公平的竞争变得至关重要。然而&#xff0c;一些不道德的投标者可能试图串通以获取不正当的竞争优势。为了解决这个问题&#xff0c;可以利用IP地址查控技术&#xff0c;确保电子招投标的公平性和透明性。本文将介绍如何通过IP地址查…

XCode15与iOS17/17.1 真机测试问题处理

XCode15与iOS17/17.1 真机测试问题处理&#xff0c;网上相关博客很多&#xff0c;摘录了如下实践后能起作用的地址如下&#xff1a;Xcode 15 报错处理 - 简书iOS17版本适配-CSDN博客 Xcode15适配-六虎 主要介绍下&#xff1a;Assertion failure in void _UIGraphicsBeginImag…

【AI视野·今日Sound 声学论文速览 第二十六期】Mon, 16 Oct 2023

AI视野今日CS.Sound 声学论文速览 Mon, 16 Oct 2023 Totally 7 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Low-latency Speech Enhancement via Speech Token Generation Authors Huaying Xue, Xiulian Peng, Yan Lu现有的基于深度学习的语音增强…

VirtualBox安装时提示失败(未解决)

似乎是升级6.1.48的时候&#xff0c;提示签名有问题。重启后就这样了。 卸载1&#xff1a; 卸载2 修复式安装

【AI视野·今日Robot 机器人论文速览 第五十五期】Mon, 16 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Mon, 16 Oct 2023 Totally 27 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;***AcTExplore, 对于未知物体的主动触觉感知。基于强化学习自动探索物体的表面形貌&#xff0c;增量式重建。(from 马里兰…

Linear、Logistic回归

线性回归 线性回归的目标是找到最佳拟合线&#xff0c;以使观测数据点与该线的残差&#xff08;实际值与预测值之间的差异&#xff09;最小化。线性回归通常用于探索变量之间的趋势、预测未来数值&#xff0c;或者用于发现因果关系。 简单实例&#xff08;波士顿房价&#xff…

文创品经营商城小程序的作用是什么

如今私域流量运营成为各商家增加的新方式&#xff0c;而在转化链路中&#xff0c;私域商城是重要的节点。 通过【雨科】平台搭建文创品小程序商城全面展示产品&#xff0c;赋能客户随时购物需求&#xff0c;摆脱第三方限制&#xff0c;自营增强生意、拓展品牌&#xff0c;分类式…

中断:Zynq Uart中断的流程和例程~UG585的CH.19

Zynq里的uart UART 控制器是全双工异步接收器和发送器&#xff0c;支持多种可编程波特率和 I/O 信号格式。该控制器可以适应自动奇偶校验生成和多主机检测模式。 UART 操作由配置和模式寄存器控制。使用状态、中断状态和调制解调器状态寄存器读取 FIFO、调制解调器信号…

10.18作业

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…

在ts中val is Map是什么意思呢?

最近在学习vue3的源码&#xff0c;顺便也在学习ts&#xff0c;就记录一下自己学习中的困惑吧 export const isArray Array.isArray export const isMap (val: unknown): val is Map<any, any> >toTypeString(val) [object Map] export const isSet (val: unknow…

【极速发表】2-4区SCI (含CCF),平均录用周期仅2个月,最快11天见刊!

一、计算机科学类SCI (11.30截稿) 【期刊概况】IF:4.0-5.0, JCR2区&#xff0c;中科院3区&#xff1b; 【检索情况】SCI在检&#xff0c;正刊&#xff1b; 【国人占比】10.58%&#xff1b; 【自引率】7.50%&#xff1b; 【年发文量】100篇以下&#xff1b; 【预警情况】无…