前言 打比赛的时候遇到了加解密脚本编写,所以想写一篇总结,正好把逆向常见的那几个加密算法也总结总结,虽然自己也不太想学密码,但是该来的跑不掉 本来这篇文章我想的标题是常见加密算法,但考虑到MD5是hash,base是编码,所以就给这篇文章起名为常见密码学算法
编码 把数据从一种格式转换为另一种格式,用于传输或存储,不需要密码
base64 Base64编码每3个字节为一个编码组,对应的24个二进制位,然后按照每6位进行分组,分为4组,计算每组的值通过映射Base64编码表获得对应的值 下述实例为base64.b64decode(Man)=TWFu
根据上述图表,一个编码组的所有种类是2的6次方,也就是base64的字符集就得有64个字符相照应,这也是64的由来,64就是字符集的大小
c代码实现
1 2 3 4 out[o++] = b64_table[(b0 >> 2 ) & 0x3F ]; out[o++] = b64_table[((b0 & 0x3 ) << 4 ) | ((b1 >> 4 ) & 0x0F )]; out[o++] = b64_table[((b1 & 0x0F ) << 2 ) | ((b2 >> 6 ) & 0x03 )]; out[o++] = b64_table[b2 & 0x3F ];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import base64table1 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=' table2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' def custom_base64_decode (data ): std_b64 = '' .join(table2[table1.index(c)] if c in table1 else c for c in data) return base64.b64decode(std_b64) def custom_base64_encode (raw_bytes ): std_b64 = base64.b64encode(raw_bytes).decode() return '' .join(table1[table2.index(c)] if c in table2 else c for c in std_b64) encoded = 'pCNxpTIUejgMhkgQdiQOdjB3bjwTdPgJgzcSdyQRgPoNcz16c44Vh31Z' flag = custom_base64_decode(encoded) print (flag.decode('utf-8' , errors='ignore' ))print (custom_base64_encode(flag))
对称加密 块密码 TEA / XTEA / XXTEA TEA(Tiny Encryption Algorithm,微型加密算法)是一种结构简单、速度快、实现容易的分组对称加密算法,曾广泛用于早期嵌入式设备、游戏加密、移动通信协议。
注意如果加密中的sum 是累加器,解密就得是累减器。累加中的sum初始加密是0,累减中的sum 就是32delta减到0的过程,当然sum不一定是0,加密中的sum 也不一定是累加 解密就是倒着来一遍,*累加变成累减,累减变成累加
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 #include <stdio.h> #include <stdint.h> void encrypt (uint32_t * v, const uint32_t * k) { uint32_t v0 = v[0 ], v1 = v[1 ]; uint32_t sum = 0 ; uint32_t delta = 0x9e3779b9 ; for (int i = 0 ; i < 32 ; i++) { sum += delta; v0 += ((v1 << 4 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); v1 += ((v0 << 4 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); } v[0 ] = v0; v[1 ] = v1; } void decrypt (uint32_t * v, const uint32_t * k) { uint32_t v0 = v[0 ], v1 = v[1 ]; uint32_t delta = 0x9e3779b9 ; uint32_t sum = delta * 32 ; for (int i = 0 ; i < 32 ; i++) { v1 -= ((v0 << 4 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); v0 -= ((v1 << 4 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); sum -= delta; } v[0 ] = v0; v[1 ] = v1; } int main () { uint32_t v[2 ] = {0x12345678 , 0x9abcdef0 }; uint32_t k[4 ] = {0x11111111 , 0x22222222 , 0x33333333 , 0x44444444 }; printf ("原文: %08X %08X\n" , v[0 ], v[1 ]); encrypt(v, k); printf ("加密: %08X %08X\n" , v[0 ], v[1 ]); decrypt(v, k); printf ("解密: %08X %08X\n" , v[0 ], v[1 ]); return 0 ; }
XTEA 是 TEA 的扩展,也称做 TEAN,四个子密钥采取不正规的方式进行混合以阻止密钥表攻击 XTEA与TEA的不同点在for循环里
1 2 3 4 5 6 7 8 v0 += (((v1 << 4 ) ^ (v1 >> 5 )) + v1) ^ (sum + key[sum & 3 ]); sum += DELTA; v1 += (((v0 << 4 ) ^ (v0 >> 5 )) + v0) ^ (sum + key[(sum >> 11 ) & 3 ]); v1 -= (((v0 << 4 ) ^ (v0 >> 5 )) + v0) ^ (sum + key[(sum >> 11 ) & 3 ]); sum -= DELTA; v0 -= (((v1 << 4 ) ^ (v1 >> 5 )) + v1) ^ (sum + key[sum & 3 ]);
前面的xtea和tea,在加密前需要两两分组,然后组内多轮相交 XXTEA 每轮都会让每个块都和它的前后块交互:每个块同时受到上下邻块的影响 → 扩散非常彻底。并且XXTEA支持任意长度(n×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 #include <stdio.h> #include <stdint.h> #include <stdlib.h> #define DELTA 0x9e3779b9 void xxtea_encrypt (uint32_t *v, int n, const uint32_t k[4 ]) { if (n < 2 ) return ; uint32_t sum = 0 ; uint32_t rounds = 6 + 52 /n; uint32_t z = v[n-1 ], y, e, p; while (rounds-- > 0 ) { sum += DELTA; e = (sum >> 2 ) & 3 ; for (p = 0 ; p < n-1 ; p++) { y = v[p+1 ]; v[p] += (((z >> 5 ) ^ (y << 2 )) + ((y >> 3 ) ^ (z << 4 ))) ^ ((sum ^ y) + (k[(p & 3 ) ^ e] ^ z)); z = v[p]; } y = v[0 ]; v[n-1 ] += (((z >> 5 ) ^ (y << 2 )) + ((y >> 3 ) ^ (z << 4 ))) ^ ((sum ^ y) + (k[(p & 3 ) ^ e] ^ z)); z = v[n-1 ]; } } void xxtea_decrypt (uint32_t *v, int n, const uint32_t k[4 ]) { if (n < 2 ) return ; uint32_t rounds = 6 + 52 /n; uint32_t sum = rounds * DELTA; uint32_t z, y = v[0 ], e, p; while (sum != 0 ) { e = (sum >> 2 ) & 3 ; for (p = n-1 ; p > 0 ; p--) { z = v[p-1 ]; v[p] -= (((z >> 5 ) ^ (y << 2 )) + ((y >> 3 ) ^ (z << 4 ))) ^ ((sum ^ y) + (k[(p & 3 ) ^ e] ^ z)); y = v[p]; } z = v[n-1 ]; v[0 ] -= (((z >> 5 ) ^ (y << 2 )) + ((y >> 3 ) ^ (z << 4 ))) ^ ((sum ^ y) + (k[(0 & 3 ) ^ e] ^ z)); y = v[0 ]; sum -= DELTA; } }
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 from Crypto.Cipher import DESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Random import get_random_byteskey = b'12345678' data = b'hello world' data_padded = pad(data, 8 ) print ("填充后的明文:" , data_padded) cipher_ecb = DES.new(key, DES.MODE_ECB) encrypted_ecb = cipher_ecb.encrypt(data_padded) print ("DES-ECB 加密:" , encrypted_ecb.hex ())decrypted_ecb_padded = cipher_ecb.decrypt(encrypted_ecb) decrypted_ecb = unpad(decrypted_ecb_padded, 8 ) print ("DES-ECB 解密:" , decrypted_ecb)iv = get_random_bytes(8 ) cipher_cbc = DES.new(key, DES.MODE_CBC, iv) encrypted_cbc = cipher_cbc.encrypt(data_padded) print ("DES-CBC 加密:" , encrypted_cbc.hex ())cipher_cbc_dec = DES.new(key, DES.MODE_CBC, iv) decrypted_cbc_padded = cipher_cbc_dec.decrypt(encrypted_cbc) decrypted_cbc = unpad(decrypted_cbc_padded, 8 ) print ("DES-CBC 解密:" , decrypted_cbc)
AES 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 from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Random import get_random_byteskey = b'1234567812345678' data = b'hello world' data_padded = pad(data, 16 ) print (data_padded) cipher = AES.new(key, AES.MODE_ECB) encrypted = cipher.encrypt(data_padded) print ("AES-ECB 加密:" , encrypted.hex ())decrypted_padded = cipher.decrypt(encrypted) decrypted = unpad(decrypted_padded, 16 ) print ("AES-ECB 解密:" , decrypted)iv = get_random_bytes(16 ) cipher_cbc = AES.new(key, AES.MODE_CBC, iv) encrypted_cbc = cipher_cbc.encrypt(data_padded) print ("AES-CBC 加密:" , encrypted_cbc.hex ())cipher_cbc_dec = AES.new(key, AES.MODE_CBC, iv) decrypted_cbc_padded = cipher_cbc_dec.decrypt(encrypted_cbc) decrypted_cbc = unpad(decrypted_cbc_padded, 16 ) print ("AES-CBC 解密:" , decrypted_cbc)
流密码 RC4 RC4 算法广泛应用于 SSL/TLS 协议和 WEP/WPA 协议。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from Crypto.Cipher import ARC4key = b'mysecretkey' data = b'Hello, World!' cipher = ARC4.new(key) encrypted_data = cipher.encrypt(data) print ("Encrypted:" , encrypted_data)cipher_dec = ARC4.new(key) decrypted_data = cipher_dec.decrypt(encrypted_data) print ("Decrypted:" , decrypted_data)
具体实现
根据密钥初始化置换盒 S,生成置换盒S
根据置换盒S生成密钥流,将明文与密钥流异或就能生成密文,密文与密钥流异或就能还原成明文。
原理很简单,上述加解密的原理本质就是异或 上述实现步骤主要分为两步,分别是初始化置换盒 S,和根据置换盒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 43 44 45 46 47 48 49 50 51 52 #include <stdio.h> #include <string.h> void rc4_init (unsigned char *s, unsigned char *key, int keylen) { int i, j = 0 ; unsigned char temp; for (i = 0 ; i < 256 ; i++) s[i] = i; for (i = 0 ; i < 256 ; i++) { j = (j + s[i] + key[i % keylen]) % 256 ; temp = s[i]; s[i] = s[j]; s[j] = temp; } } void rc4_crypt (unsigned char *s, unsigned char *data, int datalen) { int i = 0 , j = 0 , k, t; unsigned char temp; for (k = 0 ; k < datalen; k++) { i = (i + 1 ) % 256 ; j = (j + s[i]) % 256 ; temp = s[i]; s[i] = s[j]; s[j] = temp; t = (s[i] + s[j]) % 256 ; data[k] ^= s[t]; } } int main () { unsigned char key[] = "secret" ; unsigned char data[] = "Hello, RC4!" ; unsigned char s[256 ]; rc4_init(s, key, strlen ((char *)key)); rc4_crypt(s, data, strlen ((char *)data)); printf ("加密后: " ); for (int i = 0 ; i < strlen ((char *)data); i++) printf ("%02X " , data[i]); printf ("\n" ); rc4_init(s, key, strlen ((char *)key)); rc4_crypt(s, data, strlen ((char *)data)); printf ("解密后: %s\n" , data); return 0 ; }
对称加密密钥封装格式 用户输入的是口令 (password),而分组密码要的是固定长度的 key (AES-256 要 32 字节)和 iv (16 字节)。把口令变成 key/iv 的这一步叫 KDF(密钥派生) ,它本身不是加密:
口令短、熵低,直接当密钥用会被字典、暴力、彩虹表打穿
KDF 把「口令 + salt」派生成 key/iv,让同一个口令也能产出符合算法长度要求的伪随机密钥
salt 主要防预计算 (彩虹表):同一口令配不同 salt,派生结果就不同;salt 不用保密,跟密文一起存
封装格式 就是把「salt + 密文」(有时还有算法、迭代次数)按固定布局拼成文件,让解密方能复现出同一对 key/iv。
OpenSSL 的做法是:文件头放 8 字节魔数 Salted__ + 8 字节随机 salt,后面跟密文;key/iv 用 EVP_BytesToKey 从「口令 + salt」派生 —— 反复把上一块的 hash 连同口令和 salt 一起再做 hash,直到凑够 key + iv 的长度 salt 写进文件里,解密方用同一口令就能复现出同一对 key/iv
openssl enc 加密出来的文件不是裸密文,而是自带一个 16 字节的头:
1 2 3 0x00 8 字节 53 61 6c 74 65 64 5f 5f "Salted__" 0x08 8 字节 salt(随机) 0x10 ... 真正的密文
看到Salted__这 8 字节抬头就可以推断是 openssl 封装;
salt 的作用:同一个口令加密不同文件,输出各不相同,防彩虹表;但口令弱照样能爆破
密钥派生(openssl enc 默认走 EVP_BytesToKey,迭代 1 次):
1 2 3 4 D_1 = H(password || salt) D_2 = H(D_1 || password || salt) ... key || iv = (D_1 || D_2 || ...)[: key_len + iv_len]
H 在 OpenSSL 1.1.0 之后默认是 SHA-256,之前默认是 MD5
实例 rc4 +md5
1 2 3 4 5 6 7 8 9 10 from hashlib import md5from Crypto.Cipher import ARC4import base64raw = base64.b64decode('U2FsdGVkX19EMQcjfEdJWJ2vm6GxFaRQ1k+R+BDKjRXzhg==' ) salt, ct = raw[8 :16 ], raw[16 :] d1 = md5(b'key' + salt).digest() key = d1 + md5(d1 + b'key' + salt).digest() print (ARC4.new(key).decrypt(ct))
RC4 + SHA-256
1 2 3 4 5 6 7 8 9 10 from hashlib import sha256from Crypto.Cipher import ARC4import base64raw = base64.b64decode('U2FsdGVkX1+aTyx9geO1BqGbYyVuoZxa9AehvLM4AE4DyQ==' ) salt, ct = raw[8 :16 ], raw[16 :] key = sha256(b'key' + salt).digest() print (ARC4.new(key).decrypt(ct))
openssl enc -d -aes-256-cbc -md md5 -pass pass:admin123 -in enc.bin
1 2 3 4 5 6 7 8 9 10 11 12 from hashlib import md5from Crypto.Cipher import AESimport base64raw = base64.b64decode('U2FsdGVkX18+HJOxvzO7qoZxyVz5UGU771XiV2HbvqrvbETnSq2W7VKtWjV4TtPvkLO/UvUADkNSQw3nWdWVsA==' ) salt, ct = raw[8 :16 ], raw[16 :] d1 = md5(b'admin123' + salt).digest() d2 = md5(d1 + b'admin123' + salt).digest() d3 = md5(d2 + b'admin123' + salt).digest() key, iv = (d1 + d2), d3 print (AES.new(key, AES.MODE_CBC, iv).decrypt(ct))
openssl enc -d -aes-256-cbc -in enc.bin -pass pass:admin123
1 2 3 4 5 6 7 8 9 10 from hashlib import sha256from Crypto.Cipher import AESimport base64raw = base64.b64decode('U2FsdGVkX1/CKoUTTwhLXe3giWcZWcxaFviovQYuUz9RcUP8BnQMElj3qswEIWxmHVIsR/xj6cG1kivA1VqeKg==' ) salt, ct = raw[8 :16 ], raw[16 :] d1 = sha256(b'admin123' + salt).digest() d2 = sha256(d1 + b'admin123' + salt).digest() print (AES.new(d1, AES.MODE_CBC, d2[:16 ]).decrypt(ct))
非对称加密 RSA 原始rsa是纯数学加密,就是直接把明文通过数学公式变成密文,而没有额外随机性或结构。 现代rsa引入填充机制,在加密前加入随机掩码,解决纯数学加密的“同明文密文固定”问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_OAEPkey = RSA.generate(2048 ) private_key = key.export_key() public_key = key.publickey().export_key() print ("私钥:\n" , private_key.decode()[:100 ], "..." ) print ("公钥:\n" , public_key.decode()[:100 ], "..." )message = b"Hello RSA!" pub_key = RSA.import_key(public_key) cipher_encrypt = PKCS1_OAEP.new(pub_key) encrypted_data = cipher_encrypt.encrypt(message) print ("加密后:" , encrypted_data.hex ()[:60 ], "..." ) priv_key = RSA.import_key(private_key) cipher_decrypt = PKCS1_OAEP.new(priv_key) decrypted_data = cipher_decrypt.decrypt(encrypted_data) print ("解密后:" , decrypted_data.decode())
哈希函数 哈希(Hash,也叫散列或摘要)是一种单向映射函数:你能从明文得到哈希,不能从哈希反推出明文。输入任意数据 → 输出固定长度字符串(通常是十六进制)。
文件的hash ,就是将文件数据作为输入,计算出hash,这时只要修改文件,文件的hash值就会改变
MD5 MD5可以将任意长度的数据 → 映射为 128 位(16 字节)的固定长度哈希值,最后呈现结果是结果32位十六进制字符
1 2 3 4 5 6 7 8 9 10 11 12 13 import hashlibs = "hello" h = hashlib.md5(s.encode()) raw = h.digest() hex_str = h.hexdigest() print (len (raw)) print (len (hex_str)) print (raw) print (hex_str) print (hex_str == raw.hex ())
SHA 系列 SHA 跟MD5相比输出长,更安全
1 2 3 4 5 6 7 8 9 10 11 12 13 import hashlibtext = "hello world" sha1 = hashlib.sha1(text.encode()).hexdigest() sha256 = hashlib.sha256(text.encode()).hexdigest() sha512 = hashlib.sha512(text.encode()).hexdigest() print ("SHA1:" , sha1) len (sha1) len (sha1.digest()) print ("SHA256:" , sha256) print ("SHA512:" , sha512)