逆向相关算法脚本(暂时停更)

  • Base16 [已完成]
  • Base32 [已完成]
  • Base64 UUencode XXencode [已完成]
  • Base58 (Python 实现) [已完成]

  • TEA XTEA [已完成]
  • XXTEA [已完成]
  • RC4 [已完成]
  • RC5 [队列中]
  • SM4 [队列中]
  • AES [队列中]
  • DES 3DES [队列中]
  • Blowfish [队列中]
  • Chacha20 [队列中]
  • Rabbit [队列中]

  • RSA [队列中]

  • MD5 [队列中]
  • SHA256 [队列中]

  • CRC32 [队列中]

有一个 IDA 插件叫做 Findcrypt, 其工作原理是寻找“关键值”,例如 TEA 加密的 DELTA、AES 加密的 S 盒、MD5 算法的状态变量。出于安全性的原因,这些值在算法里都是被规定好不能更改的。但是万恶的出题人可不管这些,如果这些值被修改,Findcrypt 就不好用了。因此我打算做这个脚本库。

除特别说明,本文中的脚本均使用 C++ 编写,使用 VC++ 编译器,以便于在遇到“魔改”算法题目时可直接修改。


编码

Base16

实际上 Base16 编码就是将每个字符的十六进制打印出来。但是既然是一种编码,就要考虑在题目中变表的可能。

编码脚本:

 1#include <iostream>
 2#include <cstring>
 3#include <sstream>
 4
 5using namespace std;
 6
 7string dec2hex(int deci)
 8{
 9	stringstream ss;
10	ss << hex << deci;
11	return ss.str();
12}
13
14int hex2dec_onebyte(char hexi)
15{
16	stringstream ss;
17	ss << hex << hexi;
18	int deci;
19	ss >> deci;
20	return deci;
21}
22
23int main()
24{
25	string table = "0123456789ABCDEF";  // 编码表
26	char tmp0, tmp1;
27	int index0, index1;
28	string cipher = "";
29
30	string message;
31	cin >> message;
32
33	for (int i = 0; i < strlen(data(message)); i++)
34	{
35		tmp0 = dec2hex(int(message[i]))[0];
36		tmp1 = dec2hex(int(message[i]))[1];
37		index0 = hex2dec_onebyte(tmp0);
38		index1 = hex2dec_onebyte(tmp1);
39		cipher = cipher + table[index0] + table[index1];
40	}
41
42	cout << cipher;
43	return 0;
44}

解码脚本:

 1#include <iostream>
 2#include <cstring>
 3#include <sstream>
 4
 5using namespace std;
 6
 7char dec2chr(int hi4bits, int lo4bits)
 8{
 9	return char((hi4bits << 4) + lo4bits);
10}
11
12int main()
13{
14	string table = "0123456789ABCDEF";  // 编码表
15	int tmp0, tmp1;
16	string message = "";
17
18	string cipher;
19	cin >> cipher;
20
21	for (int i = 0; i < strlen(data(cipher)); i += 2)
22	{
23		tmp0 = table.find(cipher[i]);
24		tmp1 = table.find(cipher[i + 1]);
25		message = message + dec2chr(tmp0, tmp1);
26	}
27
28	cout << message;
29	return 0;
30}

特征:十六位编码表。

Base32

 1#include <iostream>
 2#include <cstring>
 3#include <bitset>
 4#include <cmath>
 5
 6using namespace std;
 7
 8string paddingeq(string cipher, string binstr)
 9{
10	int pad = (40 - (strlen(data(binstr)) % 40)) / 5;
11	for (int i = 0; i < pad; i++)
12	{
13		cipher += '=';
14	}
15	return cipher;
16}
17
18string padding0(string binstr)
19{
20	int pad = 5 - (strlen(data(binstr)) % 5);
21	for (int i = 0; i < pad; i++)
22	{
23		binstr += '0';
24	}
25	return binstr;
26}
27
28string str2binstr(string message)
29{
30	string binstr = "";
31	for (auto i : message)
32	{
33		bitset<8> bits(i);
34		binstr += bits.to_string();
35	}
36	return binstr;
37}
38
39int main()
40{
41	string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";  // 编码表
42	string binstr;
43	string cipher = "";
44	int index;
45
46	string message;
47	cin >> message;
48
49	binstr = str2binstr(message);  // 转为二进制字符串
50	if (strlen(data(binstr)) % 5 != 0)  // 填充 0
51	{
52		binstr = padding0(binstr);
53	}
54
55	for (int i = 0; i < strlen(data(binstr)); i += 5)
56	{
57		index = 0;
58		for (int j = 0; j < 5; j++)
59		{
60			if (binstr[i + j] == '1')
61			{
62				index += pow(2, 4 - j);
63			}
64		}
65		cipher += table[index];
66	}
67
68	if (strlen(data(cipher)) % 8 != 0)  // 填充等号
69	{
70		cipher = paddingeq(cipher, binstr);
71	}
72	cout << cipher;
73	return 0;
74}

解码脚本:

 1#include <iostream>
 2#include <cstring>
 3#include <bitset>
 4#include <cmath>
 5
 6using namespace std;
 7
 8string dec2bin(int deci)
 9{
10	bitset<5> bina(deci);
11	return bina.to_string();
12}
13
14int main()
15{
16	string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
17	string binstr = "";
18	string tmp;
19	int index;
20	int tmpbyte;
21	string message = "";
22
23	string cipher;
24	cin >> cipher;
25
26	for (auto i : cipher)  // 去除等号
27	{
28		if (i == '=')
29		{
30			break;
31		}
32		else
33		{
34			index = table.find(i);
35			binstr += dec2bin(index);
36		}
37	}
38
39	int rest = strlen(data(binstr)) % 8;  // 忽略用于填充的 0
40	
41	for (int i = 0; i < strlen(data(binstr)) - rest; i += 8)
42	{
43		tmpbyte = 0;
44		tmp = binstr.substr(i, 8);  // 以每组 8 bits 截取
45		for (int j = 0; j < 8; j++)
46		{
47			if (tmp[j] == '1')
48			{
49				tmpbyte += pow(2, 7 - j);
50			}
51		}
52		message += char(tmpbyte);
53	}
54
55	cout << message;
56	return 0;
57}

特征:32 位编码表,最后可能出现多个填充字符,填充字符不超过 6 个。

Base64 Uuencode XXencode

编码脚本:

 1#include <iostream>
 2
 3using namespace std;
 4
 5string encode_main(string message)
 6{
 7	string cipher = "";
 8	string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 9	for (int i = 0; i < strlen(data(message)); i += 3)
10	{
11		cipher += table[(int(message[i]) >> 2) & 0b111111];
12		cipher += table[((int(message[i]) << 4) & 0b110000) | (int((message[i + 1]) >> 4) & 0b1111)];
13		cipher += table[((int(message[i + 1]) << 2) & 0b111100) | (int(message[i + 2]) >> 6)&0b11];
14		cipher += table[(int(message[i + 2])) & 0b111111];
15	}
16	return cipher;
17}
18
19string encode_sub(string message)
20{
21	int len = strlen(data(message));  // 剩余字符串长度
22	string cipher = "";
23	string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
24
25	switch (len)
26	{
27	case 1:
28		cipher += table[(int(message[0]) >> 2) & 0b111111];
29		cipher += table[(int(message[0]) << 4) & 0b110000];
30		cipher += "==";
31		break;
32	case 2:
33		cipher += table[(int(message[0]) >> 2) & 0b111111];
34		cipher += table[((int(message[0]) << 4) & 0b110000) | (int((message[1]) >> 4) & 0b1111)];
35		cipher += table[(int(message[1]) << 2) & 0b111100];
36		cipher += '=';
37		break;
38	default:
39		break;
40	}
41
42	return cipher;
43}
44
45int main()
46{
47	string message;
48	cin >> message;
49
50	int mainstrlen = strlen(data(message)) - (strlen(data(message)) % 3);  // 去除需要填充部分
51	string mainstr = message.substr(0, mainstrlen);
52	string cipher0 = encode_main(mainstr);  // 满三字节的部分进行编码
53	
54	string substrg = message.substr(mainstrlen);
55	string cipher1 = encode_sub(substrg);  // 填充等号
56
57	string cipher = cipher0 + cipher1;
58	cout << cipher;
59	return 0;
60}
  • XXencode 本质上是一种变表 Base64,其编码表为 +-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 且无需填充字符。
  • UUencode 的算法与 Base64 有细微差异,但在实现后也是一种 Base64 变表算法,其编码表为 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_,也无需填充字符。

Base32 也可以像 Base64 一样通过判断编码后的长度填充等号,不过那样需要做多次判断;Base64 也可以像本文中 Base32 编码的脚本一样通过模运算判断需要填充等号的数量。两种方法对比学习。

解码脚本:

 1#include <iostream>
 2#include <bitset>
 3
 4using namespace std;
 5
 6string dec2bin(int deci)
 7{
 8	bitset<6> bina(deci);
 9	return bina.to_string();
10}
11
12int main()
13{
14	string binstr = "";
15	string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
16	string tmp;
17	int index;
18	int tmpbyte;
19	string message = "";
20
21	string cipher;
22	cin >> cipher;
23
24	for (auto i : cipher)
25	{
26		if (i == '=')  // 去除补位等号
27		{
28			break;
29		}
30		else
31		{
32			index = table.find(i);
33			binstr += dec2bin(index);
34		}
35	}
36
37	int rest = strlen(data(binstr)) % 8;  // 忽略用于填充的 0
38
39	for (int i = 0; i < strlen(data(binstr)) - rest; i += 8)
40	{
41		tmpbyte = 0;
42		tmp = binstr.substr(i, 8);  // 以每组 8 bits 截取
43		for (int j = 0; j < 8; j++)
44		{
45			if (tmp[j] == '1')
46			{
47				tmpbyte += pow(2, 7 - j);
48			}
49		}
50		message += char(tmpbyte);
51	}
52
53	cout << message;
54	return 0;
55}

这个解码脚本和 Base32 的解码脚本几乎一样,唯一区别就在 dec2bin() 函数中将 Base32 的 5 bits 一截变成 6 bits 一截。

特征:编码表长为 64,有形如 cipher += table[((int(message[0]) << 4) & 0b110000) | (int((message[1]) >> 4) & 0b1111)]; 这样对单个字节进行左右位移操作(需要与一些数字做按位与运算以确保只保留需要的 bits)并且最终得到长度为 6 bits 的数据。

Base58

Base58 编码的本质是将字符串 bytes_to_long() 后进行转换为 58 进制。

在这种需要使用进制转换的编码中,需要将文本的二进制首尾相接组成一个大数,但是 C++ 又有数据宽度的限制,我也懒得重复造大数运算的轮子,故这一脚本使用 Python 书写,其中用到的“轮子”仅有 Python 大数运算与 long_to_bytes() 函数。

Base58 编码脚本(58 进制转换脚本):

 1from Crypto.Util.number import bytes_to_long
 2
 3table = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"  # 编码表
 4message = input()
 5strnum = bytes_to_long(message.encode())
 6cipher = ""
 7
 8while strnum:  # 进制转换
 9    tmp = strnum % 58
10    cipher = table[tmp] + cipher
11    strnum //= 58
12
13print(cipher)

根据这个脚本,我们实际上可以轻松改出一个任意进制转换或者进制转换爆破的脚本(或者说是 BaseX 编码脚本)。

解码脚本:

 1from Crypto.Util.number import long_to_bytes
 2
 3table = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
 4cipher = input()
 5strnum = 0
 6
 7ciplen = len(cipher)
 8for i in range(ciplen):
 9    ind = table.index(cipher[ciplen - i - 1])
10    strnum += ind * 58 ** i
11
12binstr = bin(strnum)[2::]
13
14strnum = int(binstr, 2)
15message = long_to_bytes(strnum)
16print(message.decode())

特征:长得像进制转换。

对称加密

TEA XTEA

TEA 加密:

 1#include <iostream>
 2
 3using namespace std;
 4
 5const int DELTA = 0x9e3779b9;	// DELTA 为常量,是 TEA 系列加密算法的重要常数,某些题目可能会悄悄修改这个值
 6const int ROUNDS = 32;  // 迭代轮数,TEA 加密是 32 轮,XTEA 加密是 64 轮,某些题目会在这里做手脚(QQ 是 16 轮)
 7
 8void teaencrypt(unsigned int* message, unsigned int* key, unsigned int* cipher)  // 数据类型为无符号整型,下同,需要注意
 9{
10	for (int i = 0; i < 6; i += 2)  // 跳出循环的条件改成明文长度
11	{
12		unsigned int l = message[i], r = message[i + 1];
13		unsigned int k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];
14		unsigned int sum = 0;
15		for (int j = 0; j < ROUNDS; j++)  //核心加密算法
16		{
17			l += ((r << 4) + k0) ^ (r + sum) ^ ((r >> 5) + k1);
18			r += ((l << 4) + k2) ^ (l + sum) ^ ((l >> 5) + k3);
19			sum += DELTA;	
20		}
21
22		cipher[i] = l;
23		cipher[i + 1] = r;
24	}
25}
26
27int main()
28{
29	unsigned int message[6] = { 0x378dc527, 0x2809af71, 0xb3371ac9, 0x647dbb8c, 0x45afddff, 0x36abd15d };  // 应当把这里替换成真正的明文,明文长度是 8 字节的倍数
30	unsigned int key[4] = { 0xa96a5bc4, 0xac7afdb5, 0x7c8a1209, 0x350de6d0 };  // 应当把这里替换成真正的密钥
31	unsigned int cipher[6] = { 0 };  // 将密文初始化为 0
32
33	teaencrypt(message, key, cipher);
34	
35	for (int i = 0; i < 6; i++)  // 以十六进制循环输出密文
36	{
37		cout << hex << cipher[i] << ' ';
38	}
39	
40	return 0;
41}

TEA 解密脚本就是把核心加密算法中的三行倒着写一遍:

 1#include <iostream>
 2
 3using namespace std;
 4
 5const int DELTA = 0x9e3779b9;	// DELTA 为常量,是 TEA 系列加密算法的重要常数,某些题目可能会悄悄修改这个值
 6const int ROUNDS = 32;  // 迭代轮数,TEA 加密是 32 轮,XTEA 加密是 64 轮,某些题目会在这里做手脚(QQ 是 16 轮)
 7
 8void teaencrypt(unsigned int* message, unsigned int* key, unsigned int* cipher)  // 数据类型为无符号整型,下同,需要注意
 9{
10	for (int i = 0; i < 6; i += 2)  // 跳出循环的条件改成明文长度
11	{
12		unsigned int l = cipher[i], r = cipher[i + 1];
13		unsigned int k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];
14		unsigned int sum = 0xc6ef3720;  // sum 是 DELTA * ROUNDS 的结果,如果 DELTA 或 ROUNDS 变化,sum 也会变化
15		for (int j = 0; j < ROUNDS; j++)  //核心解密算法,就是将加密算法倒着写一遍
16		{
17			sum -= DELTA;
18			r -= ((l << 4) + k2) ^ (l + sum) ^ ((l >> 5) + k3);
19			l -= ((r << 4) + k0) ^ (r + sum) ^ ((r >> 5) + k1);
20		}
21
22		message[i] = l;
23		message[i + 1] = r;
24	}
25}
26
27int main()
28{
29	unsigned int cipher[6] = { 0x5ff4166b, 0x49871e4e, 0x4c64aebc, 0x90a92d2f, 0x3816c22, 0x1d233ce8 }; // 应当把这里替换成真正的密文,密文长度是 8 字节的倍数
30	unsigned int key[4] = { 0xa96a5bc4, 0xac7afdb5, 0x7c8a1209, 0x350de6d0 };  // 应当把这里替换成真正的密钥
31	unsigned int message[6] = { 0 };  // 明文长度和密文长度相等
32
33	teaencrypt(message, key, cipher);
34
35	for (int i = 0; i < 6; i++)  // 以十六进制循环输出明文
36	{
37		cout << hex << message[i] << ' ';
38	}
39
40	return 0;
41}

XTEA 加密就是将加密迭代次数 ROUNDS 改为了 64 轮。

特征:DELTA,以及核心算法的三行。

XXTEA

XXTEA 加密算法看起来与 TEA 加密算法截然不同:

 1#include <iostream>
 2
 3using namespace std;
 4
 5const int DELTA = 0x9e3779b9;
 6
 7void xxteaencrypt(unsigned int* key, unsigned int* cipher, int n)  // 核心加密算法
 8{
 9	unsigned int rounds = 6 + 52 / n;  // 计算加密轮数
10	unsigned int y, z = cipher[n - 1];
11	unsigned int sum = 0, p, e;
12
13	while (rounds-- > 0)
14	{
15		sum += DELTA;
16		e = (sum >> 2) & 3;
17
18		for (p = 0; p < n - 1; p++)
19		{
20			y = cipher[p + 1];
21			z = cipher[p] += (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
22		}
23
24		y = cipher[0];
25		z = cipher[n - 1] += (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
26	}
27}
28
29int main()
30{
31	unsigned int message[6] = { 0x378dc527, 0x2809af71, 0xb3371ac9, 0x647dbb8c, 0x45afddff, 0x36abd15d };
32	unsigned int key[4] = { 0xa96a5bc4, 0xac7afdb5, 0x7c8a1209, 0x350de6d0 };
33	unsigned int* cipher = message;
34	int n = 6;  // 数据块数量,用于在加密算法中计算加密轮数
35
36	xxteaencrypt(key, cipher, n);
37
38	for (int i = 0; i < 6; i++)
39	{
40		cout << hex << cipher[i] << ' ';
41	}
42
43	return 0;
44}

解密脚本:

 1#include <iostream>
 2
 3using namespace std;
 4
 5const int DELTA = 0x9e3779b9;
 6
 7void xxteadecrypt(unsigned int* key, unsigned int* message, int n)
 8{
 9	unsigned int rounds = 6 + 52 / n;  // 计算加密轮数
10	unsigned int y = message[0], z;
11	unsigned int sum = rounds * DELTA, p, e;
12
13	while (rounds-- > 0)
14	{
15		e = (sum >> 2) & 3;
16
17		for (p = n - 1; p > 0; p--)
18		{
19			z = message[p - 1];
20			y = message[p] -= (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
21		}
22
23		z = message[n - 1];
24		y = message[0] -= (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
25		sum -= DELTA;
26	}
27}
28
29int main()
30{
31	unsigned int cipher[6] = { 0xbe061ed4, 0x84afc0b0, 0xd533f957, 0xf12e35ab, 0xd5dd1f5e, 0xc0e97f4b };
32	unsigned int key[4] = { 0xa96a5bc4, 0xac7afdb5, 0x7c8a1209, 0x350de6d0 };
33	unsigned int* message = cipher;
34	int n = 6;
35
36	xxteadecrypt(key, message, n);  // 在发明者给出的脚本中,n 的正负决定程序执行加密还是解密,n 为正时加密,n 为负时解密,但是解密算法里实际使用到 n 的时候依然要使用正数
37
38	for (int i = 0; i < n; i++)
39	{
40		cout << hex << message[i] << ' ';
41	}
42
43	return 0;
44}

RC4

RC4 加密有一个非常显著的特点,就是加密和解密可以用同一段代码实现。类似于 ROT13,把 RC4 加密得到的密文使用相同的密码再加密一遍,就可以得到明文。理论上讲,如果在题目里遇到了 RC4 加密,可以把密文塞回程序再跑一边,明文就自己出来了。

 1#include <iostream>
 2#include <iomanip>
 3
 4using namespace std;
 5
 6void swap(int& a, int& b)
 7{
 8    int tmp = a;
 9    a = b;
10    b = tmp;
11}
12
13void init(int* S, int* T, string key)
14{
15    int keylen = key.length();
16
17    for (int j = 0; j < 256; j++)  // 初始化 S 盒、T 表
18    {
19        S[j] = j;
20        T[j] = key[j % keylen];
21    }
22}
23
24void KSA(int* S, int* T)
25{
26    int j = 0;
27    for (int i = 0; i < 256; i++)
28    {
29        j = (j + S[i] + T[i]) % 256;
30        swap(S[i], S[j]);
31    }
32}
33
34void PRGA(int* S, int* D, string message)
35{
36    int i = 0, j = 0, t;
37    for (int h = 0; h < message.length(); h++)
38    {
39        i = (i + 1) % 256;
40        j = (j + S[i]) % 256;
41        swap(S[i], S[j]);  // 生成伪随机
42        t = (S[i] + S[j]) % 256;
43        D[h] = S[t] ^ message[h];
44    }
45}
46
47int main()
48{
49    string message, key;
50    int S[256] = { 0 }, T[256] = { 0 }, D[256] = { 0 };
51    cin >> message;
52    cin >> key;
53
54    init(S, T, key);
55    KSA(S, T);
56    PRGA(S, D, message);
57
58    for (int i = 0; i < message.length(); i++)
59    {
60        printf("%02x ", D[i]);  // 输出两位十六进制数
61    }
62
63    return 0;
64}

这里是一个加密脚本,因此使用 cin 输入数据。

特征:分为两部分操作,KSA(生成密钥流)与 PRGA(生成伪随机)。