AES

浅浅学习一下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 AES
import random
from 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 AES
import os
BLOCKSIZE = 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 base64


sh = 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 AES
import random
from 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 AES
from 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 AES
from Crypto.Util.Padding import pad, unpad
from os import urandom
from flag import flag

def 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"flag = {flag}")
# print(f"key = {key}")
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 AES
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'
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
#!/usr/bin/python
from Crypto.Cipher import AES
import binascii
from Crypto.Util.number import bytes_to_long
from flag import flag
from key import key

iv = 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 xor
from Crypto.Cipher import AES
from binascii import unhexlify

tmp = 56631233292325412205528754798133970783633216936302049893130220461139160682777
#print(long_to_bytes(tmp))
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 json
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

data = 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 json
from base64 import b64decode
from Crypto.Cipher import AES

# We assume that the key was securely shared beforehand
try:
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_keycounter有关,所以我们需要求出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 AES
from Crypto.Util.strxor import *
from hashlib import sha256
from 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): #因为差了2个字节,就是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)
-------------已经到底啦!-------------