前言
本文仅针对AES加密的模式简单总结,不涉及过多数学原理知识。
AES加密解密
AES是一种对称加密方案,需要密钥与密文完成加密与解密(加密与解密采用相同的密钥),在采取不同的加密模式的时候,需要加入初始化向量(iv)。
AES采取的是块加密的方式,将明文按照16个字节一组,加密后输出16个字节长度的密文块。
想要熟练的明白AES的加密流程,最好的方式就是动手实现一边AES的加密流程。Cryptohack的对称加密教程中,提供了这个训练,如果能在不借助其他工具的情况下,手动完成AES的解密模块,有助于理解AES的解密与加密的关系。
加密流程
AES的加密流程中有几个定义需要简单了解,分别是Subbytes、ShiftRows、Columns、Add Round key。
在后面的几种加密模式和针对加密模式的攻击中,大多数没有涉及到过于底层的原理,大多数与代码的设计缺陷有关系。
在根据CryptoHack课程中,解密是从图片下到上解密的,小方格中的也是从下到上面的流程。如果检查过后还是无法解密,可以按照搜索的视频进行检查。
加密模式
AES的几种常见加密方案
ECB
ECB(电子密码本)模式,使用密钥对分块的明文加密,不需要初始化向量。
Oracle,英文指的是预言、神谕的意思,CTF中一般指的是可以通过与服务器交互的情况获取一定量信息解题的情况。

解密过程

ECB的一个重要特征是当明文块相同的情况下密文也是相同的(密钥不变),使用16字节的密钥加密16字节的明文。
举例如下 :
加密AAAABBBBCCCCDDDD
与AAAABBBBCCCCDDDD
的密文是相同的,如果改变最后一个字母,服务端返回的结果会发生变化,而且不只是一个字节发生变化,而是整个加密的块会发生很大的变化。
场景
Oracle情况下,可以通过枚举的方式来获取明文
服务端可以运行加密程序,返回的是加密之后的密文(发送的明文+加密的明文,CTF中可能是选手发送的一段明文+加密的flag),不会给出密钥。
下文中出现了两种块,“构造块”是指的用于查询的块,长度是单位块长的n倍,是16*n字节,加密的块一般指的是16个字节的单位长度。
构造两个相邻的块,第一个块可以是全部相同的字符,第二个块的长度比第一个块缺少一个字符。经过构造,返回的第二个密文块的最后一个字符就是flag的第一个加密字符。
可以通过发送AAAAAAAAAAAAAAAA
(16个A)作为第一个块,第二个块AAAAAAAAAAAAAAA
(15个A)作为第二个块,加密之后,flag的第一个字符会补充到第二个加密块的最后一个位置上,就会变成如下的形式AAAAAAAAAAAAAAAA
与AAAAAAAAAAAAAAAc
加密之后的密文,通过反复改变第一个块最后一个位置的字符,直到第一个块的密文和第二个块密文相同的时候,第一个块的最后一个字符就是flag的第一个字符。
构造块的长度需要满足密文所占用块的长度,当密文占用两个块,构造的块占用两个单位块长度(32bytes)。
具体步骤如下:
1,判断明文(flag)长度,如果明文的长度不超过一个字节,两个块的长度都是16字节,依次类推,构造的块长度总是16字节的2n倍(n是明文占用的块长,可以用n=len(m)//16+1
来计算)。
2,与服务器进行交互,当满足每两个块的密文相同的时候,去掉每个块第一个字符,将最后一个字符(枚举成功的字符)加入到结果中。枚举的明文空间可以用string
库中的string.printable
,table = string.printable[:-5]
去掉了换行符、制表符等字符(flag一般不会采用的字符)。
3,直到有明文特征的情况出现时,完成枚举明文(比如}
字符出现的时候)。
script
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
| import string
import requests
def bruteSingle():
lenth = 32 # lenth = len(flag)//16 +1
listToString = lambda x:''.join([hex(ord(x[i]))[2:] for i in range(len(x))]) # 匿名函数用于转换格式
reponse = lambda x: requests.get(url=x).text
splitCiphertext = lambda x:x.split('":"')[1][:-2]
splitString = lambda x: [x[i:i+2] for i in range(0,len(x),2)]
table = string.printable[:-5]
table = list(table)
print(table)
block1 = ['a' for i in range(lenth)]
block2 = block1.copy()[:-1] # 复制块1,并且让块2的长度小于块1,python的数组可以通过.copy() 进行复制,否则会直接在原数组上修改
url = [flag:query url] # 题目交互url
flag = []
while '}' not in flag:
for i in range(0,len(table)):
block1[-1] = table[i]
block = block1 + block2
sendBlock = listToString(block)
query = url + sendBlock
tmpResult = reponse(query)
tmp = splitCiphertext(tmpResult)
tmp = splitString(tmp)
if tmp[:31]==tmp[32:63]: # 判断第一个块与第二个块是否完全相同,这里的索引需要根据lenth更改
flag.append(block1[-1])
block1 = block1.copy()[1:]
block1.append('')
if len(block2)!=0:
block2.pop()
print(f'New block1:{block1}')
break
print(block)
res = ''.join(flag)
print(f'FLAG:{res}')
print(''.join(flag))
bruteSingle()
|
CBC
CBC(密码分组链接模式)模式,加入了初始化向量(一个16bytes的字符串),在对第一个明文块加密的时候,先将明文与IV异或运算,再用密钥将异或的结果进行加密,得到第一个块的密文,第一个块的密文同时将与第二个明文块异或,依次进行,直到加密完成。
解密是加密的逆过程,对于第一个密文快,先用密钥解密密文,再把解密之后的信息与向量异或,得到第一个明文块,再利用第一个密文块与用密钥解密第二个密文块的信息进行异或,得到第二个明文块,直到解密全部结束。
加密过程:

解密过程:

在CBC的加密与解密过程中,存在一个类似中间变量的信息,如加密过程中,明文与向量异或后的结果,作为块加密的输入,可以看作新的的明文。在解密过程中,可以发现,这个过程被逆向进行,由密文被解密之后的信息,实际上可以与向量异或,得到明文。这带来了两个可以观察出来的结论,第一个就是加密解密过程中参与异或运算得到的信息才是参与加密解密的主体,第二个就是在加密与解密过程中,这个异或的结果是不容易被改变的。这两个特征更有利于后面的攻击利用的思考(攻击的过程可改变的变量可以被限制在一定的变量范围内)。
场景
伪造cookie登录验证
在需要登录验证的情况下,如当下发cookie的明文为:admin=False=expire{xxxxx}
的格式,同时向量可以自行输入的情况下,伪造向量,使得向量与中加密的中间变量异或之后得到的明文中含有admin=True
就能完成登录。
script
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 get_cookie():
expires_at = (datetime.today() + timedelta(days=1)).timestamp()
cookie = f"admin=False;expiry={expires_at}".encode()
print(cookie)
# iv = os.urandom(16)
print('iv',iv.hex())
padded = pad(cookie, 16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(padded)
ciphertext = iv.hex() + encrypted.hex()
print('ciphertext',ciphertext)
return ciphertext
# Proof
# 伪造iv
from Crypto.Util.number import *
# {"cookie":"89d93137d13043d9ee4efaf75c2ca9a0a0f37efb466cf36ec9cf5b5a86741ff2a7af80f5f4b652c442dfb633bb5d18cb"}
cookie = "d70005d58d2b81fa136d724eb00c240affd6d12e7e1e00e7235fea77f027e07e003ddfce55fd1e8fd2450759f3823917"
# cookie = get_cookie()
initiv=cookie[:32]
print(cookie[32:])
# initiv = '25120708a0d45e54dde66e4a50032340'
old = b'admin=False;expi'
realenc = bytes_to_long(old)^int(initiv,16)
fak = b'admin=True;eexpi' # 构造虚假向量的时候需要注意split函数分割后的结果需要完全匹配,构造成b'admin=Truee;expi'分割后的第一部分为'admin=Truee',不符合规则
fakeiv = bytes_to_long(fak)^realenc
print(hex(fakeiv)[2:])
# print(check_admin(cookie[32:], hex(fakeiv)[2:]))
|
CTR
Nonce的变化模式,当Nonce没有变化的情况下,iv经过密钥加密之后的密文不变,从而使得整个加密相当于用iv与明文进行异或,补充iv到与密文相同长度,找到十六进制再异或就行。CTR模式没有类似CBC模式的密文之间的联系,所以可以采取并行的方式进行解密。
加密过程:

解密过程:

场景
图片以hex格式进行加密,按照加密解密的说明进行解密就行,需要注意的是如果win下无法打开,可以在文件管理器另存为的时候看到缩略图。
script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| enciv = long_to_bytes(int(headerOfPNG,16)^int(a[:32],16))
print(enciv.hex())
key = 'e3f227f06fd15d34d58b897cabdef07b'
# hexXor = lambda x,y:
print(len(a)//32)
paddingHex = lambda aim,x:x*(len(aim)//32)+(len(aim)%32)*'00'
hexToList = lambda x:[str(x)[i:i+2] for i in range(0,len(x),2)]
hexListXor = lambda a,b:[hex(int(a[i],16)^int(b[i],16))[2:].zfill(2) for i in range(len(a))]
print(hexListXor(hexToList(a),hexToList(paddingHex(a,key))))
flag = ''.join(hexListXor(hexToList(a),hexToList(paddingHex(a,key))))
with open('flag.txt','w') as f:
f.write(flag)
f.close()
# 以十六进制的图片,使用010editor的paste from hex进行存储为png格式即可,可使用缩略图查看
|
⚠:这里的图片无法正常打开,所以用的是缩略图, 以后有时间补充这部分。
OFB
OFB(输出反馈模式)模式是使用密钥加密初始化向量,将这个结果反复作为下一个块的初始向量,同时这个结果与对应块的明文进行异或获得对应的密文。解密过程为逆过程。
加密过程:

解密过程:

场景
虽然无法得知密钥,但是由于加密最后一步是异或,只要可以构造中间变量,就能通过异或获取明文,将原始用于加密的向量作为第二次加密的向量,将密文作为第二次加密的明文,第二次加密的结果就是明文。
script
参考
AES加密过程:https://www.youtube.com/watch?v=gP4PqVGudtg
CryptoHack symmetric:https://cryptohack.org/courses/symmetric/course_details/
维基百科:https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation