常见加密算法逆向特征分析

​ 本博文秉持不求甚解的精神,不求理解,只求看出来是什么算法。

​ 此外,本博文在AES、RC4部分有对b站up主:可厉害的土豆 视频的参考,并截取了其中的一些图片,因此本博客并非完全原创。

0x01 仿射加密

一、解密脚本

先上脚本

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#仿射密码加密与解密实现算法
# -*- coding=utf-8 -*-

#输入密钥
def accept():
k1,k2 = map(int,input('请输入两个密钥(以空格隔开):').split())
while gcd(k1, 26) !=1:
k1,k2 = map(int,input('k1和26不互素,请重新输入密钥:').split())
return k1,k2

#判断互素
def gcd(k1,m):
t = 0
while m!=0:
t = m
m = k1%m
k1 = t
return k1

#求逆元
def niyuan(k1):
n = 1
while (k1 * n) % 26 != 1:
n += 1
return n

#加密算法
def encrypt():
#输入密钥
k1,k2 = accept()

plain = input("输入明文")
c = []
for i in range(len(plain)):
#小写字母
if plain[i].islower():
c.append(chr(((ord(plain[i])-97)*k1+k2)%26+97))
#大写字母
elif plain[i].isupper():
c.append(chr(((ord(plain[i])-65)*k1+k2)%26+65))
#其他
else :
c.append(plain[i])

cipher = ''.join(c)
print(cipher)
print('加密完成!')


#解密算法
def decrypt():
#输入密钥
k1,k2 = accept()
#逆元
ny = niyuan(k1)


cipher = input("输入密文")
p = []
for i in range(len(cipher)):
#小写字母
if cipher[i].islower():
t1 = ord(cipher[i])-97-k2
if t1 < 0:
t1 +=26
p.append(chr((ny * t1)%26+97))
#大写字母
elif cipher[i].isupper():
t2 = ord(cipher[i])-65-k2
if t2 < 0:
t2 +=26
p.append(chr((ny * t2)%26+65))
#其他
else :
p.append(cipher[i])

plain = ''.join(p)
print(plain)
print('解密完成!')


if __name__ == '__main__':
while True:
ch = int(input("加密请输入【1】:\n解密请输入【2】:\n退出请输入【3】:"))
if ch == 1:
encrypt()
elif ch == 2:
decrypt()
elif ch == 3:
exit()
else:
print("请输入1或2")

二、程序情况

1、基本概况

仿射就是利用如:

image-20220625184110096

的表达式,通过密钥a、b密文x进行加密。

2、特征

一般来说,因为仿射加密加密的内容只能是英文的26个字母,因此一般程序一开始会对于输入做限制:

然后加密就这么写:

1
2
3
4
5
for(i=0;i<len;++i){
temp=array[i]-'a';
temp = (temp*key_a+key_b)%26;
array[i]=temp+'a';
}

0x02 DES

DES加密是一种经典的分组、对称加密算法。

一、解密脚本

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def desenc(key, plaintext):
plaintext = pad(plaintext, 8)
print("* DES Encryption *")
print(b"key: " + key)
print(b"plaintext: " + plaintext)
des = DES.new(key, DES.MODE_ECB)
ciphertext = des.encrypt(plaintext)
# ciphertext = binascii.b2a_hex(ciphertext)
print(ciphertext)
return ciphertext


def desdec(key, ciphertext):
print("* DES Decryption *")
print(b"key: " + key)
print(b"ciphertext: " + ciphertext)
des = DES.new(key, DES.MODE_ECB)
plaintext = des.decrypt(ciphertext)
# plaintext = binascii.b2a_hex(plaintext)
print(plaintext)
return plaintext

def transfer(s):
# a = "\\x" + s.replace("h, ", "\\x").replace("h", "")
# a = a.split("\\x")[1:]
# l = []
# for i in a:
# if len(i) == 3 and i[0] == '0':
# i = i[1:]
# l.append(i)
# r = "".join(l)
l = s.split(", ")
r = []
for i in l:
if len(i) == 1:
r.append("0" + i)
elif len(i) == 3:
r.append(i[:2])
elif len(i) == 4:
r.append(i[1:3])
r = "".join(r)
return r

if __name__ == "__main__":
# # DES
desenc(b'1234567890123456', b'python spider!')
desdec(b'1234567890123456', desenc(b'1234567890123456', b'python spider!'))

二、程序情况

des好就好在,虽然过程有点复杂,但是他的常量多的起飞。

1、基本情况:

​ 密钥长 64 位,密钥事实上是 56 位参与 DES 运算(第 8、16、24、32、40、48、56、 64 位是校验位,使得每个密钥都有奇数个 1),分组后的明文组和 56 位的密钥按位替代或交换的方法形成密文组。

​ 其主要加密流程有16轮

image-20220625205203397

2、常量使用

ip置换:

1
2
3
4
5
6
const char IP_Table[64]= {
58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7
};

S盒置换:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const char S_Box[8][4][16] = {
// S1
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
// S2
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
// S3
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
// S4
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
// S5
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
// S6
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
// S7
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
// S8
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
};

P盒置换:

1
2
3
4
const char P_Table[32] = {
16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25
};

0x03 AES

​ AES加密是一种经典的分组、对称加密算法,其出现是为了代替安全性不够的DES算法。

一、解密脚本

类型转化可以参考上文DES中的transform函数

1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*- 
from Crypto.Cipher import AES

password = b'1234567890123456' #秘钥,b就是表示为bytes类型
cipher= b'\xfc\xad\x71\x5b\xd7\x3b\x5c\xb0\x48\x8f\x84\x0f\x3b\xad\x78\x89\xd0\xe7\x09\xd0\xff\xd3\x8c\x6d\xfe\xc5\x5c\xcb\x9f\x47\x5b\x01'
aes = AES.new(password,AES.MODE_ECB) #创建一个aes对象
den_text = aes.decrypt(cipher) # 解密密文
print("answer is:",den_text)

二、程序情况

1、基本情况

​ 在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:

AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

2、加密流程

分组后,每一组的加密流程如下:

image-20220625184844702

​ 如图,前9轮每轮有四步,最后一轮不进行列混合。

3、特征情况

​ 注意,这里不涉及到AES的加密流程讲解,只涉及到特征。

密钥拓展:

​ 轮函数加密一定需要密钥,因此密钥拓展一定发生在一开始:

​ 密钥拓展函数中,会进行两个循环:

第一个用于求w0-w3,第二个用于求w4-w43

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 /* W[0-3] */
for (i = 0; i < 4; ++i) {
LOAD32H(w[i], key + 4*i);
}

/* W[4-43] */
for (i = 0; i < 10; ++i) {
w[4] = w[0] ^ MIX(w[3]) ^ rcon[i]; //常量rcon
w[5] = w[1] ^ w[4];
w[6] = w[2] ^ w[5];
w[7] = w[3] ^ w[6];
w += 4;
}
//其中:
#define LOAD32H(x, y) \
do { (x) = ((unsigned int)((y)[0] & 0xff)<<24) | ((unsigned int)((y)[1] & 0xff)<<16) | \
((unsigned int)((y)[2] & 0xff)<<8) | ((unsigned int)((y)[3] & 0xff));} while(0)

#define MIX(x) (((S[BYTE(x, 2)] << 24) & 0xff000000) ^ ((S[BYTE(x, 1)] << 16) & 0xff0000) ^ \
((S[BYTE(x, 0)] << 8) & 0xff00) ^ (S[BYTE(x, 3)] & 0xff))

两个循环的为代码中,都会有如上所示的移位和异或运算。

同时,如上代码块所示,还有重要常量rcon:

1
2
3
4
static const unsigned int rcon[10] = {
0x01000000UL, 0x02000000UL, 0x04000000UL, 0x08000000UL, 0x10000000UL,
0x20000000UL, 0x40000000UL, 0x80000000UL, 0x1B000000UL, 0x36000000UL
};

初始变换:

​ 就是一顿的异或。

轮函数:

​ 轮函数中,我们涉及到四个函数:

  • subBytes(state); 字节代换
  • shiftRows(state);行移位
  • mixColumns(state); 列混合
  • addRoundKey(state, rk); 轮密钥加

字节代换中我们会用到非常重要的常量S盒(S盒一样基本就实锤了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned char S[256] = {
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};

字节代换步骤,会将状态矩阵与s盒进行代换处理。

行移位就是简单移位操作,没有特征;

列混合中,我们需要用到矩阵:

1
2
3
4
M[4][4] = {{0x02, 0x03, 0x01, 0x01},
{0x01, 0x02, 0x03, 0x01},
{0x01, 0x01, 0x02, 0x03},
{0x03, 0x01, 0x01, 0x02}};

用于进行列混合操作,这个函数的逻辑相对比较复杂。

轮密钥加,就是一堆的异或。

0x04 SM4

​ SM4是中国特有的分组加密算法,分组长度为128bit,密钥为128bit,轮数为32

一、解密脚本

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
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
52
53
54
55
import binascii
from gmssl import sm4


class SM4:
def __init__(self):
self.crypt_sm4 = sm4.CryptSM4() # 实例化

def str_to_hexStr(self, hex_str):
"""
字符串转hex
:param hex_str: 字符串
:return: hex
"""
hex_data = hex_str.encode('utf-8')
str_bin = binascii.unhexlify(hex_data)
return str_bin.decode('utf-8')

def encryptSM4(self, encrypt_key, value):
"""
国密sm4加密
:param encrypt_key: sm4加密key
:param value: 待加密的字符串
:return: sm4加密后的十六进制值
"""
crypt_sm4 = self.crypt_sm4
crypt_sm4.set_key(encrypt_key.encode(), sm4.SM4_ENCRYPT) # 设置密钥
date_str = str(value)
encrypt_value = crypt_sm4.crypt_ecb(date_str.encode()) # 开始加密。bytes类型
return encrypt_value.hex() # 返回十六进制值

def decryptSM4(self, decrypt_key, encrypt_value):
"""
国密sm4解密
:param decrypt_key:sm4加密key
:param encrypt_value: 待解密的十六进制值
:return: 原字符串
"""
crypt_sm4 = self.crypt_sm4
crypt_sm4.set_key(decrypt_key.encode(), sm4.SM4_DECRYPT) # 设置密钥
decrypt_value = crypt_sm4.crypt_ecb(bytes.fromhex(encrypt_value)) # 开始解密。十六进制类型
return decrypt_value.decode()
# return self.str_to_hexStr(decrypt_value.hex())


if __name__ == '__main__':
key = "f38fc9b32af486e65d6f93dbc41b9123"
strData = "90897h8789thvht"
SM4 = SM4()
print("原字符:", strData)
encData = SM4.encryptSM4(key, strData) # 加密后的数据,返回bytes类型
print("sm4加密结果:", encData)

decData = SM4.decryptSM4(key, encData)
print("sm4解密结果:", decData) # 解密后的数据

二、程序情况

1、常量使用

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
26
27
28
29
30
31
32
33
34
35
static const unsigned char SboxTable[16][16] =
{
{ 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05 },
{ 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99 },
{ 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62 },
{ 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6 },
{ 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8 },
{ 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35 },
{ 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87 },
{ 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e },
{ 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1 },
{ 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3 },
{ 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f },
{ 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51 },
{ 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8 },
{ 0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0 },
{ 0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84 },
{ 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48 }
};

/* System parameter */
static const unsigned long FK[4] = { 0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc };

/* fixed parameter */
static const unsigned long CK[32] =
{
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
};

0x05 rc4

​ rc4是流密码,其逻辑比较简单,明文和密文一样长,通过简单的异或(明文XOR密钥)加密进行加密,其核心就是给定一个随意长度的密钥,生成伪随机数的密钥生成算法。

​ 不好的是,rc4没有任何常量标识,这使得不熟悉的情况下难以辨认。

一、解密脚本

​ 建议不要使用库函数,因为有时候编写者会稍微改变一点rc4的逻辑,这时候我们需要进行修正,库函数不能修正。

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
26
27
28
def init_sbox(key) :
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
#print(s_box) #for_test
return s_box


def encode(box,plain) :
res=''
i = j =0
for s in plain:
i = (i + 1) %256
j = (j + box[i]) %256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j])% 256
k = box[t]
#print(k)
res=res+(chr((s^(~k))&0xFF)) #直接与每一个字节明文相异(注意这里就是根据题意进行了修正,本来是不需要取反的!)
print(res)

if __name__ =='__main__':
key = 'secrets' #填写key
data = [0x10, 0x2C, 0x02, 0xFC, 0xFB, 0x3B, 0x0D, 0x73, 0x6E, 0xBC, 0xB9, 0xA7, 0x6F, 0x2F, 0x00] #填写密文
box = init_sbox(key)
encode(box,data)

二、程序情况

1、主程序思路

rc4加密主程序如下:

1
2
3
4
init_sbox();
for(i=0;i<len;++i){
result[i]=plaintext[i]^generate_key();
}

可以看到非常简单:

​ 第一步:根据用户给出的不定长密钥,初始化密钥;

​ 第二部:将密钥与明文进行异或,得到密文;

​ 看到这样的异或加密,我们就可以猜测是一种流密码,但是我们依然需要进一步了解**init_box函数 和 generate_key函数 **的逻辑,这样才能判断是哪一种流密码,其逻辑如下

  • 一、初始化S表 (init_sbox)

    • Step1:对S表进行线性填充,⼀般为256个字节;
    • Step2:用种子密钥填充另⼀个256字节的K表;
    • Step3:用K表对S表进行初始置换。
  • 二、密钥流的生成generate_key(为每个待加密的字节⽣成⼀个伪随机数,⽤来异或)

    ​ 注:表S⼀旦完成初始化,种⼦密钥就不再被使用。

2、特征

由于没有特征值,所以我们只能对照逻辑硬看,感觉八九不离十的,应该就是rc4

init_sbox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//c参考实现思路
void init_sbox(){
int i,j;
int key_len = strlen((const char *)key);
unsigned char temp;
for(i=0;i<256;++i)
sbox[i]=i;
j=0;

for(i=0;i<256;++i){
j=(j+sbox[i]+key[i%key_len])%256;
temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
}
}

简单的说,这个函数就是初始化了一个大小256,记录从1到256的数组,称为s盒

然后通过我们的输入(key)的值,循环对盒进行元素的换位置,将s盒中数字的顺序打乱

generate_key:

我们用打乱的S盒,可以进行密钥的生成

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char generate_key(){
unsigned char temp;
int t;

pos_i=(pos_i+1)%256;
pos_j = (pos_j+sbox[pos_i])%256;
temp = sbox[pos_i];
sbox[pos_i] = sbox[pos_j];
sbox[pos_j] = temp;
t = (sbox[pos_i]+sbox[pos_j])%256;
return sbox[t];
}

​ 这个函数的特征就是,没有循环,也不需要传参,直接就是一个逻辑运算,然后返回一个值。与此同时他就已经改变了S盒的构造,下一次运行时,又会返回一个新的值。

0x06 MD5

一、解密脚本

​ md5和hash都是哈希算法,都是单向的,所以不存在加解密,也不存在密钥,只有明文和密文。发现是md5或sha之后,直接就是一个爆破,一般来说题目出得都应该是四位数,因此脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import hashlib
from string import digits,ascii_letters,punctuation,whitespace,printable

dic = '_'+digits+ascii_letters
#dic = digits
cipher='fae8a9257e154175da4193dbf6552ef6'

def test():
for a in dic:
for b in dic:
for c in dic:
for d in dic:
t = str(a) + str(b) + str(c) +str(d)
#print(t)
amd5 = hashlib.md5(t.encode(encoding='UTF-8')).hexdigest()#md5(),sha1(),sha256(),sha512()
if amd5 == cipher: #32/40/64/128 自己找哈希值并改变长度
print('\n成功解出明文: '+amd5+' -> '+t)
return

test()

二、程序情况

1、输入输出:

输入输出:输入明文不定长,输出永远是16字节;

2、主要函数和加密思路:

​ 先看md5的主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   size_t filledLen;
char *filledData;
unsigned int A,B,C,D;
unsigned int *M = (unsigned int*)malloc(GROUP_SIZE);

filledLen = md5_update(&filledData,input,inLen);

md5_init(&A,&B,&C,&D);

for(i = 0;i < filledLen / GROUP_SIZE;i++){
md5_transform(M, filledData + i * 64); //char to int
data_round(&A,&B,&C,&D,M);
}

sprintf(out,"%08x%08x%08x%08x",shift(A),shift(B),shift(C),shift(D));

free(M);
M = NULL;
free(filledData);
filledData = NULL;

return;
  • 第一步,补位(filledLen),
    • 使得输入的长度位512*n+448(单位为bit);补位通过形如10000000….的比特位进行。
    • 然后再补64位,用于记录原始信息长度。
    • 补位结束后,信息长度为512*n bit;
  • 第二步,初始化(md5_init):
    • 定义四个四字节标准幻数,总大小十六字节,作为循环运算的初始值;(固定的,后文会写);
    • 四个标准幻数应以小端字节序存储;
  • 第三步,混淆(64位一组):
    • 使用FF、GG、HH、II四个函数,参与混淆过程;
    • 最终输出结果位四个标准幻数混淆后的值。

3、特征

​ 我们并不关系混淆的逻辑,只关心算法的特征。

补位操作:

​ 补位操作中一般都可能会按照字节进行操作,那么我们就可以看到类似64和56(448/4)的常量,且补位时需要先补充一个1,因此如果按照字节补充,就会出现0x80

初始化:

​ 初始化操作不在循环中进行,同时,初始化的四个幻数是固定的:

1
2
3
4
int A=0x67452301
int B=0xEFCDAB89
int C=0x98BADCFE
int D=0x10325476

​ 就是1到E再从E到1,只不过是小端存储

​ 因此,有时候也会以数组的形式顺序定义,然后通过memcpy等函数进行进一步的小端存储。

混淆:

​ 混淆肯定是循环,循环轮数是补位后的长度除以64,且其使用的函数FF、GG、HH、II逻辑非常有识别度。

​ 其函数中会存在大量的调用如下图:

image-20220625153028084

​ 每个函数调用16次,且传参为七个

​ 进一步进入,FF、GG、HH、II的逻辑都是简单的位运算,不具有分支循环逻辑。

0x07 sha256

一、解密脚本

​ sha256脚本与md5通用,在上文中修改函数名即可,不多赘述。

二、程序情况

​ sha256是sha2下的分支哈希函数。

1、输入输出

​ 对于任意长度的消息,SHA256 都会产生一个 256bit 长的哈希值,相当于是个长度为 32 个字节的数组,通常用一个长度为 64 的十六进制字符串来表示。

2、主要函数和加密思路:

主函数思路如下:

1
2
3
4
5
6
7
8
9
sha_init(&A,&B,&C,&D,&E,&F,&G,&H);

filledLen = sha_update(&filledData,input,inLen);

for(i = 0;i < filledLen / GROUP_SIZE;i++){
sha_transform(M, filledData + i * 64); //char to int
data_round(&A,&B,&C,&D,&E,&F,&G,&H,M);
}
sprintf(out,"%08x%08x%08x%08X%08x%08x%08x%08x",A,B,C,D,E,F,G,H);
  • 第一步,初始化sha_init:

    • 和md5相似,sha256也需要初始化一个标准幻数,对其进行处理最后达成输出;
    • 他们也是以小端进行存储,一共8个,每个四字节,是通过质数平方根的小数求得,但是不重要,因为是固定的(见下文);
  • 第二步,修正输入sha_update:

    • 和md5相同,sha修正的思路完全一致;
    • 使得输入的长度位512*n+448(单位为bit);补位通过形如10000000….的比特位进行。
    • 然后再补64位,用于记录原始信息长度。补位结束后,信息长度为512*n bit;
  • 第三步,轮加密:

    • 先使用sha_transform进行初步对于数据的类型转化处理;
    • 轮加密的整体思路就是分块进行,先把输入分成定长大小的块,然后再把每个块在分块进行逻辑加密;

3、特征

初始化:

​ 标准幻数是最容易识别的:

1
2
3
4
5
6
7
8
a=0x67, 0xE6, 0x09, 0x6A,
b=0x85, 0xAE, 0x67, 0xBB,
c=0x72, 0xF3, 0x6E, 0x3C,
d=0x3A, 0xF5, 0x4F, 0xA5,
e=0x7F, 0x52, 0x0E, 0x51,
f=0x8C, 0x68, 0x05, 0x9B,
g=0xAB, 0xD9, 0x83, 0x1F,
h=0x19, 0xCD, 0xE0, 0x5B

​ 注意,实际顺序是小端,即上面的倒序;

轮加密:

​ 轮加密中,我们会用到一个重要的巨大常量,即K表,由上代码中data_round调用:

image-20220625180922642

看到这样的常量标志,就非常具有意义了,其值如下:

1
2
3
4
5
6
7
8
9
10
unsigned int const K[64] ={
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

此外:

​ 处理函数sha_transform应由嵌套循环组成,具体取决于书写者使用的数据结构。

​ 轮函数data_round应该由循环次数为16 、64 、64的三个循环的主体构成。