• 对于加密按照对加密数据的处理可分为流加密和块加密,块加密的算法一般都在对称加密中体现,而这里只介绍块加密工作模式,以及相应的攻击。

  • 块加密的工作模式分为以下几类:ECB模式CBC模式CTR模式CFB模式OFB模式GCM模式,接下来逐一介绍这些工作模式的具体过程。以及这些模式的相关攻击,有些攻击会有对应的CTF题目为例题。

  • 下面这些块加密模式都以AES块加密算法为例子,我们并不需要具体了解AES加密算法的过程。

明文处理

  • 对于块加密,我们都要对明文进行分组处理,既然对明文要分组处理,这就会导致分得的最后一组会存在不足,此时我们就要用特定的数据对分组后的明文进行填充操作
  • 块加密的分组方式一般还取决于块加密的一些算法,比如使用AES加密算法一般就是将明文分为128位为一组,而使用DES加密一般就是将明文分为64位为一组
  • 所以我们一开始会对明文进行如下的分组处理:
    • 明文被分成固定大小的块(每块多少位取决于加密算法)
    • 如果明文长度不是块大小的整数倍,最后一个块需要填充(通常使用PKCS#7填充
    • :有些填充方式如果明文长度是块大小的整数倍,那么最后还要填充一整块。
  • 接下来简单介绍一下一些填充方式:
    • PKCS#7(最常用):补多少字节就填充该字节的值,比如最后一块差3个字节才满,此时就要补3个0x03。如果是要填充一整块的话,假设每块16字节,此时就要填充16个0x10。该填充方式在满足明文长度是块大小整数倍的时候还要填充一整块
    • ISO/IEC 7816-4:在尾部填充一个0x80,然后剩下的填充0,在明文长度是块大小整数倍的时候还要填充一整块
    • ANSI_X.923:全部填充0x00,最后一个字节填充差的长度,比如差4个字节需要这样填充0x00 0x00 0x00 0x04在明文长度是块大小整数倍的时候还要填充一整块
    • Zero_Padding:填充部分全部填充0x00,并且在明文长度是块大小整数倍的时候不需要额外填充一整块
    • ISO_10126(已弃用):填充的时候全部填充随机数,只有最后一个字节填充差的长度(类似于ANSI_X.923),在明文长度是块大小整数倍的时候还要填充一整块

ECB模式

工作方式

AES-ECB加密首先输入明文,首先将这些明文转化为16进制的ASCII值;其次将这些明文以16字节为一组进行分块处理。如果明文的字节数不是16进制,那么就会将最后一块明文块,按照某种填充方式,使其达到16字节。

image-20250618015927172

接下来就是使用AES加密对每一个明文块单独加密,明文块与明文块之间相互独立。

然后明文块再与密钥一起,经过AES加密,输出密文块。密文块同样也是128bit

image-20250618020037783

注意:ECB加密模式要注意的是,每个明文块都是使用相同的密钥进行加密的。这也就是说相同的明文会输出相同的密文。

image-20250618020105293

  • 总结:ECB的工作模式其实就是这么简单,就是利用加密算法,对每一个明文块单独加密,明文块与明文块之间相互独立,没有什么联系。所以ECB这种简单的块加密方式就非常容易被选择明文攻击

选择明文攻击

  • 该方法是利用相同的明文块经过加密后,会产生相同的明文块。这样就可以进行逐个字节的爆破,从而得到flag

  • 这里直接以CTF题入手,题目描述如下:

image-20250618020445058

  • 中文翻译过来如下:

image-20250618020456679

  • 先nc一下连接靶机,看看靶机上的程序是如何运行的,发现nc过后系统会将flag经过AES-ECB加密后的密文输出过来

image-20250618020529553

  • 然后再看密文上面的字,可以得到信息,用户可以输入base64编码的数据,然后靶机会重新加密,并输出重新加密过后的密文

image-20250618020546785

确定明文个数

  • 所以我们可以进行如下攻击,首先确定明文个数,先将得到的密文进行base64解码,解码成16进制的形式

image-20250618020640151

  • 然后以两个十六进制数为1组,进行加密的明文一共有多少字节多少块。发现密文解码后一共有48个字节,所以按照16字节为一组的明文块,一共就有3块。初步得出明文flag的长度在32-48字节字节
1
2
3
a = '1F478C9B50E20C146B2204BDFBF8CD85D3439B280CB59A64E5E0EDD7D99F03C4BFC4606660026280E174A847FFB02A24'
print(len(a)//2)
# 48
  • 然后再进行明文填充,先输入a查看密文字节数有没改变,如果没有改变再输入aa。如果还是没有改变继续增加a的个数,然后输入进去,再检查发送过来的密文字节数
  • 这里发现当发送一个a过后密文的字节数就改变了,这就可以得到明文flag的长度为47个字节这里为什么为47个字节,而不是48个字节,是与明文块的填充方式有关
1
2
3
4
5
a
YQ==
Ox9sMgy8iPF+LWBlBwtjJGBSmwwwbV7xXyxQlUuyTQ3FLNVyTeQw42geSIGnLBj7YS+OVO/90ZQVLsGHBSULcQ==
3B1F6C320CBC88F17E2D6065070B632460529B0C306D5EF15F2C50954BB24D0DC52CD5724DE430E3681E4881A72C18FB612F8E54EFFDD194152EC18705250B71
64字节

image-20250618020739115

爆破明文原理

  • 然后确定了明文长度,就可以通过相同明文加密后的密文是相同的进行逐个字节爆破,最后得到flag。
  • 逐个字节爆破过程如下,这里为了更好的演示就直接利用本题flag进行爆破演示,从而得到flag
1
grodno{a50f00AES_1n_ECB_m0de_1s_hackableaf1aef}

image-20250618020818179

  • 爆破过程如下,先构造payload='aaaaaaaaaaaaaaa?(可见字符)aaaaaaaaaaaaaaag'
  • 然后对发送出来的密文块1,密文块2进行判断是否相同,如果相同即可得出可见字符(?)对应的字符是g,那么flag第一个字符就给爆破出来了

image-20250618020841374

  • 然后再进行第二个字符的爆破,之后就这样重复爆破下去

image-20250618020855739

exp

  • 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
from pwn import *
import base64
from Crypto.Cipher import AES
import string
def recv():
p.recvuntil(b'ciphertext (b64): ')
return base64.b64decode(p.recvline().strip())
def send(m):
p.sendline(base64.b64encode(m.encode('utf-8')))
p = remote('ctf.mf.grsu.by',9016)
p.recvuntil(b'secret ciphertext (b64): ')
enc_flag = base64.b64decode(p.recvline().strip())
print(enc_flag)
charr = string.printable
l = b'a'
length= 48
flag = ''
for i in range(47):
for ch in charr:
send('a'*(length-1)+flag+ch+'a'*(length-1))
res=recv()
panduan = res[:48]
en = res[48:96]
if panduan==en:
flag+=ch
length-=1
print(flag)
break
print(flag)

image-20250618020930322

CBC模式

工作方式

密文填充攻击(Padding Oracle)

参考文章:AES-CBC密文填充攻击—深入理解和编程实现 | 网络热度

参考文章:Padding Oracle攻击解密AES - poziiey - 博客园

Padding Oracle Attack是由VaudenayEurocrypt 2022首次提出。由于要满足块加密16字节为一块,通常最后一块可能不能被填满,这时就需要使用一些填充规则进行填充操作。并且将密文发送回服务器解密时,如果解密后不满足填充规则服务器会给出填充错误的提示。这就导致可以进行Padding Oracle Attack

  • 利用前提条件

    • 需要采用PKCS5这个填充标准,填充值为需要填充的字节数。
    • 使用AES-CBC加密算法
    • 攻击者需要获得密文,以及初始向量IV
    • 攻击者可以通过服务器知道padding是否正确
  • 攻击效果:可以在未知key的条件下解出密文

  • 这里直接模拟一个加密和验证padding的服务,使用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
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
from Crypto.Cipher import AES
import sys
import random
import os
flag = "flag{test_flag}"
# flag is in A-Z、a-z、{}、_
class Padding_Oracle:

def __init__(self,text):
self.key = os.urandom(16)
self.cipher = AES.new(self.key,AES.MODE_CBC)
self.chall = text.encode('utf-8')

self.iv = os.urandom(16)

def pad(self,s):
padbit = 16 - len(s) % 16
padding = bytes([padbit] * padbit)
return s + padding

def get_chall(self):
aes = AES.new(self.key,AES.MODE_CBC,self.iv)
return self.iv+aes.encrypt(self.pad(self.chall))

def unpad(self,s):
padbit = s[-1]
padding = s[-padbit:]
if set(padding) == {padbit}:
return s[:-s[-1]]
else:
return None

def decrypt(self,ciphertext):
cipher = AES.new(self.key,AES.MODE_CBC,self.iv)
plaintext = cipher.decrypt(ciphertext)

return plaintext

def oracle(self,ciphertext):
plaintext = self.decrypt(ciphertext)
if self.unpad(plaintext) == None:
return False
else:
return True

if __name__ == "__main__":
print("Welcome to Padding oracle attack!!!")
oracle = Padding_Oracle(flag)
print(oracle.get_chall().hex())
print("begin your attack")
while True:
ciph = bytes.fromhex(input("ct:"))
if len(ciph) % 16 != 0:
print("ct must be a multiple of 16 bytes.")
continue
print("Oracle says: ", oracle.oracle(ciph))


字节翻转攻击

CTR模式

CFB模式

OFB模式

GCM模式