浅浅学习一下AES加密的几种模式
AES加密只能加密128bit的块,如果明文长度不足128bit,那就需要进行填充
CTR模式不需要填充
ECB模式 电码本模式,ECB (Electronic Codebook Book)
ECB模式加解密流程:
ECB模式首先要_把明文分块,然后用相同的加密方式和密钥进行加密,这就会导致同样的明文加密后得到的是同样的密文 $$ 设明文分块为x_i,密钥为key,加密函数为E_k,解密函数为D_k $$
$$ \therefore 加密后的密文c_i = E_k(x_i) $$
$$ 解密: $$
$$ m_i = D_k(c_i) $$
使用Cipher库里有加解密ECB的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from Crypto.Cipher import AESimport randomfrom Crypto.Util.number import *key = random.getrandbits(128 ) key = long_to_bytes(key) aes = AES.new(key,AES.MODE_ECB) m = b'abcdefghijklmnop' c = aes.encrypt(m) print (c)plaintext = aes.decrypt(c) print (plaintext)
值得注意的是,明文m和密钥key的长度必须是16的倍数
SWPU2020 ECB 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 from Crypto.Cipher import AESimport osBLOCKSIZE = 16 flag = os.environ['FLAG' ] def pad (data ): pad_len = BLOCKSIZE - (len (data) % BLOCKSIZE) if len (data) % BLOCKSIZE != 0 else 0 return data + chr (pad_len) * pad_len def unpad (data ): num = ord (data[-1 ]) return data[:-num] def enc (data,key ): cipher = AES.new(key,AES.MODE_ECB) encrypt = cipher.encrypt(pad(data)) return encrypt def dec (data,key ): try : cipher = AES.new(key,AES.MODE_ECB) encrypt = cipher.decrypt(data) return unpad(encrypt) except : exit() def task (): try : key = os.urandom(16 ) while True : plaintext = raw_input("Amazing function: " ).decode('hex' ) yusa = plaintext+flag print enc(yusa,key).encode('hex' ) except Exception as e: print str (e) exit() if __name__ == "__main__" : task()
通过输入几次数据判断flag长度大概为44
根据ECB加密的特性。
输入b'00'×47
(服务器把16进制转成byte数据,2个16进制数据变成一个byte),服务器会返回b'00'×47
+flag[0]
的密文
我们多次输入b'00'×47
+chr(i)
如果获得同样的密文,那么这个chr(i)
就是flag[0]
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *from Crypto.Util.number import *host = 'node5.anna.nssctf.cn' port = 28632 sh = remote(host,port) n = b"00" * 47 flag = b'' for i in range (44 ): sh.recvuntil(b"Amazing function: " ) sh.sendline(b"00" *(47 -i)) c = sh.recvline().decode() for j in range (0x11 ,0xff ): data = sh.recvuntil(b"Amazing function: " ) sh.sendline(n+ hex (j)[2 :].encode()) m = sh.recvline().decode() if m[:96 ] == c[:96 ]: flag += long_to_bytes(j) print (flag) n = n[2 :] + hex (j)[2 :].encode() break
逐字节爆破 这种题目,题目会给出整段flag的密文,我们拥有加密器,便可实现攻击
例题1来自Vishwa
CryptCTF2024 本题没有源码
翻译后
1 2 3 4 5 6 7 8 您将在ECB(电子本)模式下获得由AES算法生成的密文。加密密钥未知。 密文包含你应该找到的标志。所以你的任务是在ECB模式下破解AES算法 您可以访问加密设备,并且可以每次重复使用相同的密钥对任意数据进行加密。 您输入的数据将被添加到标志的前面,您将收到一个新的密文。 注意! ! !您输入的数据必须是base64编码!!
这题flag长度为48,即以下分组 $$ m_1…m_{16} \quad m_{17}…m_{32}\quad m_{33}…m_{48} $$ 我们先输入15个0,于是有这样的分组 $$ 000000000000000m_1 \quad m_2…m_{17} \quad m_{18}…m_{33} \quad m_{34}…m_{48} $$ 记此时对应的密文为enc_flag
爆破第一段
爆破前16位的时候,我们只需要输入000000000000000 + X
此时分组为 $$ 000000000000000X \quad m_2…m_{17} \quad m_{18}…m_{33} \quad m_{34}…m_{48} $$ 记此时对应的密文是c
我们对比c[:16]
和enc_flag[:16]
即可判断X是否正确
爆破第二段
输入15个0,有这样的分组 $$ 000000000000000m_1 \quad m_2…m_{17} \quad m_{18}…m_{33} \quad m_{34}…m_{48} $$ 记此时对应的密文为enc_flag
我们再输入flag[:-15] + X + 000000000000000
$$ m_2…m_{16}X \quad 00000000000000m_1 \quad m_2…m_{17} \quad m_{18}…m_{33} \quad m_{34}…m_{48} $$ 记此时对应的密文是c
我们对比c[:16]
和enc_flag[16:32]
即可判断X是否正确
爆破第三段
输入15个0,有这样的分组 $$ 000000000000000m_1 \quad m_2…m_{17} \quad m_{18}…m_{33} \quad m_{34}…m_{48} $$ 记此时对应的密文为enc_flag
我们再输入flag[:-15] + X + 000000000000000
此时分组如下 $$ m_{18}…m_{32}X \quad 000000000000000m_{1} \quad m_{2}…m_{17} \quad m_{18}…m_{33} $$ 记此时对应的密文是c
我们对比c[:16]
和enc_flag[32:48]
即可判断X是否正确
exp
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 from pwn import *from tqdm import *import base64sh = remote("ctf.mf.grsu.by" ,9016 ) sh.recvuntil(b"secret ciphertext (b64):" ) ciphertext = sh.recvline() sh.recvline() flag = "" for i in trange(16 ): sh.sendline(base64.b64encode((b"0" *(15 -i)))) c = sh.recvline().strip().decode().split(":" )[-1 ].strip() enc_flag = base64.b64decode(c.encode()) for j in range (33 ,128 ): msg = (("0" *(15 -i)) + flag + chr (j)).encode() sh.sendline(base64.b64encode(msg)) cc = sh.recvline().strip().decode().split(":" )[-1 ].strip() cipher = base64.b64decode(cc) if cipher[:16 ] == enc_flag[:16 ]: flag += chr (j) print (f"flag:{flag} " ) break for i in trange(16 ): sh.sendline(base64.b64encode((b"0" *(15 -i)))) c = sh.recvline().strip().decode().split(":" )[-1 ].strip() enc_flag = base64.b64decode(c.encode()) for j in range (33 ,128 ): msg = (flag[-15 :] + chr (j) + "0" *(15 -i)).encode() sh.sendline(base64.b64encode(msg)) cc = sh.recvline().strip().decode().split(":" )[-1 ].strip() cipher = base64.b64decode(cc) if cipher[:16 ] == enc_flag[16 :32 ]: flag += chr (j) print (f"flag:{flag} " ) break for i in trange(16 ): sh.sendline(base64.b64encode((b"0" *(15 -i)))) c = sh.recvline().strip().decode().split(":" )[-1 ].strip() enc_flag = base64.b64decode(c.encode()) for j in range (33 ,128 ): msg = (flag[-15 :] + chr (j) + "0" *(15 -i)).encode() sh.sendline(base64.b64encode(msg)) cc = sh.recvline().strip().decode().split(":" )[-1 ].strip() cipher = base64.b64decode(cc) if cipher[:16 ] == enc_flag[32 :48 ]: flag += chr (j) print (f"flag:{flag} " ) break
CBC模式 密码分组链接模式,CBC(Cipher Block Chaining)
在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行ECB加密后得到密文分组。对于第一块明文,引入初始向量 iv
这个概念
每个明文块应该是128bit,或者128的倍数
python实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from Crypto.Cipher import AESimport randomfrom Crypto.Util.number import *iv = random.getrandbits(128 ) iv = long_to_bytes(iv) key = random.getrandbits(128 ) key = long_to_bytes(key) print (key,iv,sep='\n' )aes = AES.new(key,AES.MODE_CBC,iv) m = b'abcdefghijklmnop' c = aes.encrypt(m) print (c)key = b'\xd4\xc5\x0f\xf3\x89\xd3[\x94\xbc\xde\xacds\xae\xf3\x1b' iv = b'\xf0\x90\xd1\x99\r\xb1\xa7\x81\xa8\xae\xbbQ\xec\xaa\xef\x10' c = b'\x95{\x80\xffS\x14L^\x9e?\x08~y\nH1' decryption = AES.new(key,AES.MODE_CBC,iv) plaintext = decryption.decrypt(c) print (plaintext)
例题 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 from Crypto.Cipher import AES import os iv = os.urandom(16) key = os.urandom(16) my_aes = AES.new(key, AES.MODE_CBC, iv) flag = open('flag.txt', 'rb').read() flag += (16 - len(flag) % 16) * b'\x00' c = my_aes.encrypt(flag) print(list(c), list(iv), list(key)) ''' [137, 163, 60, 145, 236, 127, 76, 5, 212, 171, 46, 211, 161, 172, 41, 198, 117, 247, 140, 226, 169, 248, 208, 245, 214, 44, 180, 9, 170, 59, 205, 234] [138, 237, 90, 59, 60, 190, 103, 179, 137, 128, 10, 206, 237, 10, 183, 174] [170, 90, 227, 119, 123, 155, 185, 38, 148, 37, 159, 42, 221, 36, 2, 57] '''
这里已经给出密文,初始向量,密钥。只需把他们先转成byte,然后再按流程解密
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Cipher import AESfrom Crypto.Util.number import *iv = [138 , 237 , 90 , 59 , 60 , 190 , 103 , 179 , 137 , 128 , 10 , 206 , 237 , 10 , 183 , 174 ] key = [170 , 90 , 227 , 119 , 123 , 155 , 185 , 38 , 148 , 37 , 159 , 42 , 221 , 36 , 2 , 57 ] c = [137 , 163 , 60 , 145 , 236 , 127 , 76 , 5 , 212 , 171 , 46 , 211 , 161 , 172 , 41 , 198 , 117 , 247 , 140 , 226 , 169 , 248 , 208 , 245 , 214 , 44 , 180 , 9 , 170 , 59 , 205 , 234 ] Iv = b'' Key = b'' C = b'' for i in iv: Iv += long_to_bytes(i) for i in key: Key += long_to_bytes(i) for i in c: C += long_to_bytes(i) DecryptKey = AES.new(Key,AES.MODE_CBC,Iv) flag = DecryptKey.decrypt(C) print (flag)
[HNCTF 2022 WEEK3]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 from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom os import urandomfrom flag import flagdef cbc_encrypt (msg: bytes ): msg = pad(msg, 16 ) msg = [msg[i:i+16 ] for i in range (0 , len (msg), 16 )] key = urandom(16 ) out = [] for block in msg: cipher = AES.new(key, AES.MODE_ECB) next = cipher.encrypt(block) out.append(next ) key = next out = b"" .join(out) return key, out def main (): key, ct = cbc_encrypt(flag*3 ) print (f"ct = {ct} " ) if __name__ == "__main__" : main() """ ct = b'\x179\xb8l\x97\xbew\xc2\xd5f~\x8e\xdc\xf2\x9b\xabR\xa9a\xd2\xf4\xde\xd6|\xd1\x9f\xe9q\x1d\xfcm\xfbj\xe9\x9e\xab\xf5fL\xb3\xb5_\xa5\x16\x8e\x7f\x9fV`\x8b\x16\xa1\xa6)\x08\x97\x91\xbd3\x1d\xeb\\\x86\xa2\xd6\x94>\xf3\xfdt\xd9\x14\xf3\xfc\xe2\x02\xd6\xc4\xcfq"\x1a\x14~2]4\x9f\xc9\x88\xf8\x12\xb6\xa2\xd7\xec\x0b\x7f\xd4d\xdc\xc6\xb4]\x10u\xc6f\x97m\xccA\x82\x02\xa5gh\x85\x85Wz\xd9.\xff\x9bx\x99J\x0e\x86\x16\x90\xad\x1e\x17\x86\x95\xb8S\x17\xea\x93v\xd0' """
msg = pad(msg, 16)
,将msg填充为16的倍数
msg = [msg[i:i+16] for i in range(0, len(msg), 16)]
,将msg分为每组16字节
key = urandom(16)
,随机生成一个16字节的加密密钥
1 2 3 4 5 6 7 for block in msg: cipher = AES.new(key, AES.MODE_ECB) next = cipher.encrypt(block) out.append(next) key = next out = b"".join(out) return key, out
对每组明文进行ECB加密,并且将加密后的密文作为下一组加密的密钥。我们除了第一组密文无法解密,其他密文都可以解出
1 2 3 4 5 6 7 8 9 10 from Crypto.Cipher import AESct = b'\x179\xb8l\x97\xbew\xc2\xd5f~\x8e\xdc\xf2\x9b\xabR\xa9a\xd2\xf4\xde\xd6|\xd1\x9f\xe9q\x1d\xfcm\xfbj\xe9\x9e\xab\xf5fL\xb3\xb5_\xa5\x16\x8e\x7f\x9fV`\x8b\x16\xa1\xa6)\x08\x97\x91\xbd3\x1d\xeb\\\x86\xa2\xd6\x94>\xf3\xfdt\xd9\x14\xf3\xfc\xe2\x02\xd6\xc4\xcfq"\x1a\x14~2]4\x9f\xc9\x88\xf8\x12\xb6\xa2\xd7\xec\x0b\x7f\xd4d\xdc\xc6\xb4]\x10u\xc6f\x97m\xccA\x82\x02\xa5gh\x85\x85Wz\xd9.\xff\x9bx\x99J\x0e\x86\x16\x90\xad\x1e\x17\x86\x95\xb8S\x17\xea\x93v\xd0' ct = [ct[i:i+16 ] for i in range (0 , len (ct), 16 )] plain = b'' for i in range (1 , len (ct)): cipher = AES.new(ct[i-1 ], AES.MODE_ECB) plain += cipher.decrypt(ct[i]) print (plain)
[安洵杯 2020]easyaes 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 from Crypto.Cipher import AESimport binasciifrom Crypto.Util.number import bytes_to_longfrom flag import flagfrom key import keyiv = flag.strip(b'd0g3{' ).strip(b'}' ) LENGTH = len (key) assert LENGTH == 16 hint = os.urandom(4 ) * 8 print (bytes_to_long(hint)^bytes_to_long(key))msg = b'Welcome to this competition, I hope you can have fun today!!!!!!' def encrypto (message ): aes = AES.new(key,AES.MODE_CBC,iv) return aes.encrypt(message) print (binascii.hexlify(encrypto(msg))[-32 :])''' 56631233292325412205528754798133970783633216936302049893130220461139160682777 b'3c976c92aff4095a23e885b195077b66' '''
求初始向量
首先注意到$key$和$hint$的长度不符,所以给的$hint$中有一半是原来os.urandom(4)
的值。
可以通过这一特点恢复$key$
然后依次求前一块密文,最后求得iv
exp:
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 from Crypto.Util.number import *from Crypto.Util.strxor import strxor as xorfrom Crypto.Cipher import AESfrom binascii import unhexlifytmp = 56631233292325412205528754798133970783633216936302049893130220461139160682777 hint = b'}4$d' *8 key = long_to_bytes(tmp^bytes_to_long(hint)) msg = b'Welcome to this competition, I hope you can have fun today!!!!!!' cipher = '3c976c92aff4095a23e885b195077b66' c4 = bytes .fromhex(cipher) msgblock = [msg[i*16 :(i+1 )*16 ] for i in range (len (msg)//16 )] def decrypt (c ): aes = AES.new(key,AES.MODE_ECB) return aes.decrypt(c) c3 = xor(decrypt(c4),msgblock[3 ]) c2 = xor(decrypt(c3),msgblock[2 ]) c1 = xor(decrypt(c2),msgblock[1 ]) flag = b'NSSCTF{' + xor(decrypt(c1),msgblock[0 ]) + b'}' print (flag)
CTR模式 计算器模式,CTR(Counter)
在CTR模式中 COUNTER
是整个CTR模式的核心所在。它是由IV经过一定的规则之后生成的一段数据,长度与数据块的长度相等
COUNTER
通过ECB加密后得到一个 COUNTER
的密文,再和明文1进行异或得到密文1。在加密结束后 COUNTER
的值加1,然后再次用ECB加密,依次加密
加密example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import jsonfrom base64 import b64encodefrom Crypto.Cipher import AESfrom Crypto.Random import get_random_bytesdata = b"secret" key = get_random_bytes(16 ) cipher = AES.new(key, AES.MODE_CTR) ct_bytes = cipher.encrypt(data) nonce = b64encode(cipher.nonce).decode('utf-8' ) ct = b64encode(ct_bytes).decode('utf-8' ) result = json.dumps({'nonce' :nonce, 'ciphertext' :ct}) print (result){"nonce" : "XqP8WbylRt0=" , "ciphertext" : "Mie5lqje" }
解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import jsonfrom base64 import b64decodefrom Crypto.Cipher import AEStry : b64 = json.loads(json_input) nonce = b64decode(b64['nonce' ]) ct = b64decode(b64['ciphertext' ]) cipher = AES.new(key, AES.MODE_CTR, nonce=nonce) pt = cipher.decrypt(ct) print ("The message was: " , pt) except (ValueError, KeyError): print ("Incorrect decryption" )
这里给道ECB和CTR结合的题目
例题 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 from Crypto.Util.number import * from Crypto.Cipher import AES from Crypto.Util import Counter from hashlib import sha256 from secret import flag import os def padding(msg): return msg + os.urandom(16 - len(msg) % 16) msg = b"where is the flag? Key in my Heart/Counter!!!!" key = b"I w0nder how????" assert len(msg) == 46 assert len(key) == 16 enc_key = os.urandom(16) initial_value = bytes_to_long(enc_key) hash = sha256(str(initial_value).encode()).hexdigest() aes = AES.new(enc_key, AES.MODE_ECB) enc_flag = aes.encrypt(padding(flag)) ctr = Counter.new(AES.block_size * 8, initial_value=initial_value) aes = AES.new(key, counter=ctr, mode=AES.MODE_CTR) enc = aes.encrypt(msg) print("enc = {}".format(enc[-16:])) print("enc_flag = {}".format(enc_flag)) print("hash = {}".format(hash)) ''' enc = b'\xbe\x9bd\xc6\xd4=\x8c\xe4\x95bi\xbc\xe01\x0e\xb8' enc_flag = b'\xb2\x97\x83\x1dB\x13\x9b\xc2\x97\x9a\xa6+M\x19\xd74\xd2-\xc0\xb6\xba\xe8ZE\x0b:\x14\xed\xec!\xa1\x92\xdfZ\xb0\xbd\xb4M\xb1\x14\xea\xd8\xee\xbf\x83\x16g\xfa' hash = efb07225b3f1993113e104757210261083c79de50f577b3f0564368ee7b25eeb '''
flag
是用 enc_key
再用ECB模式加密的,我们的目的就是求出 enc_key
而 enc_key
和 counter
有关,所以我们需要求出 counter
的末值,再往前倒
先把msg分组,msg的长度是46,分为16,16,14三组
通过msg的后14位和enc的后14位进行异或得到加密后的 enc_counter
这里需要注意的是:counter是通过ECB加密的 ,解出 counter
之后,减去2就是初始的 counter
也就是 initial_value
解 enc_counter
的时候要填充,因为他只有14字节,要填充为16字节
得到 initila_value
之后就有了加密flag的 enc_key
,然后就是解ECB得到flag
exp:
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 from Crypto.Util.number import *from Crypto.Cipher import AESfrom Crypto.Util.strxor import *from hashlib import sha256from tqdm import *key = b"I w0nder how????" msg = b"where is the flag? Key in my Heart/Counter!!!!" enc = b'\xbe\x9bd\xc6\xd4=\x8c\xe4\x95bi\xbc\xe01\x0e\xb8' enc_flag = b'\xb2\x97\x83\x1dB\x13\x9b\xc2\x97\x9a\xa6+M\x19\xd74\xd2-\xc0\xb6\xba\xe8ZE\x0b:\x14\xed\xec!\xa1\x92\xdfZ\xb0\xbd\xb4M\xb1\x14\xea\xd8\xee\xbf\x83\x16g\xfa' hash = 'efb07225b3f1993113e104757210261083c79de50f577b3f0564368ee7b25eeb' enc_counter = strxor(msg[-14 :],enc[-14 :]) for i in trange(2 **8 ,2 **16 ): pad = long_to_bytes(i) Enc_counter = enc_counter + pad aes_ecb = AES.new(key,AES.MODE_ECB) counter = aes_ecb.decrypt(Enc_counter) initial_counter = bytes_to_long(counter) - 2 hash_counter = sha256(str (initial_counter).encode()).hexdigest() if hash_counter == hash : enc_key = long_to_bytes(initial_counter) ECB = AES.new(enc_key,AES.MODE_ECB) flag = ECB.decrypt(enc_flag) print (flag)