更新日志
2022年12月14日9时0分
1. 使用 unsigned long long 代替 long long 防止 UB.
2. 随机数 rand 对 0x8000 取模,使各环境保持一致.
2022年12月19日11时0分
1. 更新下载链接
〇、目录
一、思路来源
二、实现过程
1. 密钥生成
2. 一次加密
3. 二次加密
三、成品分享
1. 库文件&使用说明
2. 打包程序
一、思路来源
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,仍然无法了解信息的内容。——百度
加密算法的作用就是把一段信息(明文)通过某种算法,转化为不能/不易看懂的信息(密文)。
作用:防止个人情报的泄漏,防止数据被更改,确保数据发自特定的一方、传给特定的一方。
由上文可知,加密在当今信息时代的重要性,于是我就设计了一个加密、解密算法。
二、实现过程
1. 密钥生成
我构想的加密算法,可以让接受者清楚加密的时间。于是我就想到了“UNIX时间戳”,C++中用 <ctime>
可以方便地生成。
#include <ctime>
using namespace std;
unsigned long long t1 = time(0);
但是C++中的 time(0)
是按秒计的,也就是说,一秒内这样生成的所有密钥都会是一致的;而且如果其他人知道了加密的大概时间,就可以很容易地暴力枚举进行破解了。
于是,我在 time(0)
的基础上,乘上一个 rand()
。因为不同环境下 rand()
的结果不同(但都是 0 到至少 0x7fff),所以对 0x8000 取模。
#include <ctime>
#include <cstdlib>
using namespace std;
srand(time(0));
unsigned long long t1 = time(0);
unsignedlong long t2 = rand() % 0x8000;
然后就是怎么把这两个数转换为一个密钥了。我用了非常简单的算法:×
。没错,就是乘法……
但是这样生成的随机数是在一个较小的范围 [0, 32767]
内的,所以我将随机数平方,再乘以时间戳。为了“美观”,再乘上一个大数 20090312
。(猜猜这是什么意思 'u')
你可能已经发现了,我用了 unsigned long long
存储数据,超过最大值后会“溢出”,自动取模:
在 C++ 中
int 溢出是 Undefined Behavior,是否取会模取决于编译器。
unsigned int 溢出是 Define Behavior,在溢出时自动取模。
#include <ctime>
#include <cstdlib>
using namespace std;
srand(time(0));
unsigned long long t1 = time(0);
unsignedlong long t2 = rand() % 0x8000;
unsigned long long t = t1 * (t2 * t2) * 20090312; // 习惯性打括号
这么大的数,一时想不到怎么用来加密字符串,于是我有了类似 base64
的一种编码方式,将这个数转换为一个字符串:先生成“字典”,然后用“进制转换”的方式将数字转换为大进制数,再对应为字符串。
然后我把整个算法打包为一个 class
类,方便调用。
// File: pcode.h
#include <ctime>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <string>
#pragma once
using namespace std;
class _PCODE
{
private:
// typedef
typedef unsigned long long ll;
typedef pair<ll, ll> PLL;
// rand % 0x7fff
// get time
PLL get_time()
{
ll t1 = time(0);
//ll t2 = clock();
ll t2 = rand() % 0x8000;
return {t1, t2};
}
// encode
vector<char> ppr_code;
// time to str
string time_str(PLL tt)
{
ll t = tt.first * (tt.second * tt.second) * 20090312;
string res;
while (t)
{
res.push_back(ppr_code[t % ppr_code.size()]);
t /= ppr_code.size();
}
return res;
}
public:
_PCODE()
{
srand(time(0));
for (int i = 0 ; i < 26 ; ++i)
{
if (i % 2 == 0)
{
ppr_code.push_back('a' + i);
ppr_code.push_back('A' + i);
}
else
{
ppr_code.push_back('A' + i);
ppr_code.push_back('a' + i);
}
if (i % 3 == 0)
ppr_code.push_back('0' + i / 3);
}
ppr_code.push_back('9');
ppr_code.push_back('+');
ppr_code.push_back('=');
ppr_code.push_back('!');
ppr_code.push_back('-');
ppr_code.push_back('@');
ppr_code.push_back('$');
ppr_code.push_back('%');
ppr_code.push_back('&');
}
// get code (now)
string getnow(ll *t1, ll *t2)
{
PLL t = get_time();
*t1 = t.first;
*t2 = t.second;
return time_str(t);
}
string getnow()
{
PLL t = get_time();
return time_str(t);
}
// number to code
string encode(ll t1, ll t2)
{
PLL t = {t1, t2};
return time_str(t);
}
} pcode;
2. 一次加密
在上一节中,我们已经生成了字符串密钥,接下来就是加密的过程了。(这一节叫做“一次加密”,说明接下来还有一次加密过程)
算法很简单,重复密钥多次,使密钥与明文长度一致,或者只取密钥的前一部分。然后密钥与明文的每一位进行 ^
抑或运算。
(代码与下一节合在一起)
3. 二次加密
看到上面那节,你发现问题了吗?
没错,抑或运算的结果对应的符号有部分不在可见符号范围内(我用的是 ANSI
编码),于是,我用 Base64
编码进行了二次编码。
Base64编码算法请见(论坛里找的):https://www.52pojie.cn/thread-1715164-1-1.html。这里我加了一个 namespace
命名空间,方便调用。
// File: base64.h
#include <string>
#include <cstring>
#pragma once
namespace _base64
{
// 编码表
static const char base64en[] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', '+', '/'
};
// 解码表
static const unsigned char base64_suffix_map[256] =
{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255,
255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255
};
std::string base64_encode(const char *data)
{
std::string out = "";
// 长度计算
int data_len = strlen(data);
if (data_len == 0)
{
return "";
}
char c = '\0';
char last_c = '\0';
for (int i = 0; i < data_len; i++)
{
c = data[i];
switch (i % 3)
{
case 0:
out.push_back(base64en[(c >> 2) & 0x3f]);
break;
case 1:
out.push_back(base64en[(last_c & 0x3) << 4 | ((c >> 4) & 0xf)]);
break;
case 2:
out.push_back(base64en[((last_c & 0xf)) << 2 | ((c >> 6) & 0x3)]);
out.push_back(base64en[c & 0x3f]);
break;
}
last_c = c;
}
if (data_len % 3 == 1)
{
out.push_back(base64en[(c & 0x3) << 4]);
out.push_back('=');
out.push_back('=');
}
if (data_len % 3 == 2)
{
out.push_back(base64en[(c & 0xf) << 2]);
out.push_back('=');
}
return out;
}
std::string base64_decode(const char *data)
{
std::string output;
int data_len = strlen(data);
unsigned char c = '\0';
int t = 0, y = 0;
int g = 3;
for (int x = 0; x < data_len; x++)
{
c = base64_suffix_map[data[x]];
if (c == 255)
output.clear();
if (c == 253)
continue; // 对应的值是换行或者回车
if (c == 254)
{
c = 0; // 对应的值是'='
g--;
}
t = (t << 6) | c;
if (++y == 4)
{
output.push_back((t >> 16) & 0xff);
if (g > 1)
output.push_back((unsigned char)((t >> 8) & 0xff));
if (g > 2)
output.push_back((unsigned char)(t & 0xff));
y = t = 0;
}
}
return output;
}
}
编码总程序(库文件)。这里我加了一个 class
类,方便调用。
// File: ppr.h
#include <string>
#include <cstring>
#include "pcode.h"
#include "base64.h"
#pragma once
using namespace std;
class _PPRCode
{
private:
//typedef
typedef unsigned long long ll;
public:
// 编码
string encode(string str, ll *t1, ll *t2)
{
string pwd = pcode.getnow(t1, t2);
string ans;
for (int i = 0, j = 0 ; i < str.size() ; ++i, ++j)
{
if (j == pwd.size())
j = 0;
ans += char(str[i] ^ pwd[j]);
}
ans = _base64::base64_encode(ans.c_str());
return ans;
}
// 解码
string decode(string str, ll t1, ll t2)
{
str = _base64::base64_decode(str.c_str());
string pwd = pcode.encode(t1, t2);
string ans;
for (int i = 0, j = 0 ; i < str.size() ; ++i, ++j)
{
if (j == pwd.size())
j = 0;
ans += char(str[i] ^ pwd[j]);
}
return ans;
}
} pprc;
三、成品分享
1. 库文件&使用说明
库文件源码上面有(有 // File: *.h
的都是)。方便下载,打包一个云盘:
链接:https://www.123pan.com/s/JavSVv-NqLnH
提取码:52pj
使用说明:
// 1. 加入头文件
#include "ppr.h"
// 2. 编码
std::string str = "RainPPR"; // 明文(std::string)
unsigned long long t1; // 将要返回的密钥1
unsigned long long t2; // 将要返回的密钥2
std::string ans = pprc.encode(str, &t1, &t2); // ans 中的即为密文
// 3. 解码
std::string str = "Jg4FKHQ9Jg=="; // 密文(std::string)
unsigned long long t1 = 1670900298; // 密钥1
unsigned long long t2 = 17993; // 密钥2
std::string ans = pprc.decode(str, t1, t2); // ans 中的即为明文
2. 打包程序
成品发在原创区了,详见:https://www.52pojie.cn/thread-1726292-1-1.html