RainPPR 发表于 2022-12-13 11:11

【第二版】自己设计的一个加密、解密算法;C++实现

本帖最后由 RainPPR 于 2022-12-19 11:12 编辑

声明:此算法完全是学习,未考虑算法效率、内存占用等。仅供学习使用!
## 更新日志
```
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. 密钥生成

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我构想的加密算法,可以让接受者清楚加密的时间。于是我就想到了“UNIX时间戳”,C++中用 `<ctime>` 可以方便地生成。
```cpp
#include <ctime>

using namespace std;

unsigned long long t1 = time(0);
```

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;但是C++中的 `time(0)` 是按秒计的,也就是说,一秒内这样生成的所有密钥都会是一致的;而且如果其他人知道了加密的大概时间,就可以很容易地暴力枚举进行破解了。
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;于是,我在 `time(0)` 的基础上,乘上一个 `rand()`。因为不同环境下 `rand()` 的结果不同(但都是 0 到至少 0x7fff),所以对 0x8000 取模。
```cpp
#include <ctime>
#include <cstdlib>

using namespace std;

srand(time(0));
unsigned long long t1 = time(0);
unsignedlong long t2 = rand() % 0x8000;
```

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;然后就是怎么把这两个数转换为一个密钥了。我用了非常简单的算法:`×`。没错,就是乘法……
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;但是这样生成的随机数是在一个较小的范围 `` 内的,所以我将随机数平方,再乘以时间戳。为了“美观”,再乘上一个大数 `20090312`。(~~猜猜这是什么意思 'u'~~)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;你可能已经发现了,我用了 `unsigned long long` 存储数据,超过最大值后会“溢出”,自动取模:
```
在 C++ 中
int 溢出是 Undefined Behavior,是否取会模取决于编译器。
unsigned int 溢出是 Define Behavior,在溢出时自动取模。
```
```cpp
#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; // 习惯性打括号
```

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这么大的数,一时想不到怎么用来加密字符串,于是我有了类似 `base64` 的一种编码方式,将这个数转换为一个字符串:先生成“字典”,然后用“进制转换”的方式将数字转换为大进制数,再对应为字符串。
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;然后我把整个算法打包为一个 `class` 类,方便调用。
```cpp
// 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();
                        }

                        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. 一次加密

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在上一节中,我们已经生成了字符串密钥,接下来就是加密的过程了。(这一节叫做“一次加密”,说明接下来还有一次加密过程)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;算法很简单,重复密钥多次,使密钥与明文长度一致,或者只取密钥的前一部分。然后密钥与明文的每一位进行 `^` 抑或运算。
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(代码与下一节合在一起)

### 3. 二次加密

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;看到上面那节,你发现问题了吗?
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;没错,抑或运算的结果对应的符号有部分不在可见符号范围内(我用的是 `ANSI` 编码),于是,我用 `Base64` 编码进行了二次编码。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Base64编码算法请见(论坛里找的)**:[https://www.52pojie.cn/thread-1715164-1-1.html](https://www.52pojie.cn/thread-1715164-1-1.html)。这里我加了一个 `namespace` 命名空间,方便调用。
```cpp
// 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 =
        {
                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;
                        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);
                                        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];

                        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;
        }
}
```

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**编码总程序(库文件)**。这里我加了一个 `class` 类,方便调用。
```cpp
// 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 ^ pwd);
                        }

                        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 ^ pwd);
                        }

                        return ans;
                }
} pprc;
```

## 三、成品分享

### 1. 库文件&使用说明

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;库文件源码上面有(有 `// File: *.h` 的都是)。方便下载,打包一个云盘:
> 链接:(https://www.123pan.com/s/JavSVv-NqLnH)
> 提取码:52pj

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**使用说明**:

```cpp
// 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. 打包程序

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;成品发在原创区了,详见:[https://www.52pojie.cn/thread-1726292-1-1.html](https://www.52pojie.cn/thread-1726292-1-1.html)在这里也感谢我的同学——徐梓玉,她给这个软件带来了一点灵感。

lsy_loren 发表于 2022-12-13 11:25

膜拜大神!

binqi 发表于 2022-12-13 11:30

期待楼主的成品

飘零星夜 发表于 2022-12-13 11:53

真棒, 我目前使用得就是类似得算法,RC4 +BASE64 。 用以实现空中升级这个二逼功能。

seawaycao 发表于 2022-12-13 12:10

谢谢分享,收藏备用

嘛名字不名字的 发表于 2022-12-13 22:00

能搞一个CPK后缀名的解密软件吗?

chinasugar 发表于 2022-12-14 01:22

很不错的思路,学习中。

sxqxyfw 发表于 2022-12-14 09:13

谢谢分享,收藏学习以备以后借鉴使用。正好我也经常使用C++

MarTao2020 发表于 2022-12-31 01:32

在这里的都是大佬

cnqv 发表于 2023-1-3 20:30

谢谢分享
页: [1] 2
查看完整版本: 【第二版】自己设计的一个加密、解密算法;C++实现