Automne's Shadow.

CBC MAC Forgery Conclusion

2019/07/20 Share

Crypto

与HMAC不同的是,CBC-MAC表示使用CBC模式计算消息认证码的一种方式,一般是将最后一组密文作为MAC返回。

参考https://github.com/ashutosh1206/Crypton/tree/master/Message-Authentication-Code/CBC-MAC-Forgery

如下图所示

automne

从图中可以看到,最终返回的MAC为Encrypt(y),所以如果想要实现MAC的伪造,就需要得到两个相同的y

P1表示第一组明文,P2表示第二组明文,C1表示第一组密文

由上图可以得到下面的公式:

1
2
y = Encrypt(P1 xor IV) xor P2
= C1 xor P2

如果明文分组P1只有一个block,那么MAC(P1) = C1

如果在P1的基础上将明文分组增加到两个block,比如P1’ = P1 + P2,那么MAC(P1’) = Encrypt(MAC(P1) xor P2),注意y = MAC(P1) xor P2

假设我们现在想要得到一个明文Q1’和P1‘内容不同但是得到的MAC是一致。

假设一个block的明文分组Q1对应的MAC为MAC(Q1),为了得到相同的MAC,那么第二组的明文分组Q2 = MAC(Q1) xor y = MAC(Q1) xor MAC(P1) xor P2

这样一来在Q1的基础上将明文分组增到到两个block,就得到了Q1’ = Q1 + Q2

那么MAC(Q1’) = Encrypt(MAC(Q1) xor Q2) = Encrypt(MAC(Q1) xor MAC(Q1) xor MAC(P1) xor P2) = Encrypt(MAC(P1) xor P2) = MAC(P1’)

可以看到,两个不同的分组,得到了相同的MAC,这就是CBC MAC Forgery

这里有一个ECB-MAC的例子

源码如下

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
from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
from binascii import hexlify, unhexlify

def sign(key, message):
try:
ECB = AES.new(key, AES.MODE_ECB)
messageblocks = [message[i:i + 16] for i in range(0, len(message), 16)]
tag = ECB.encrypt(messageblocks[0])
for i in range(1,len(messageblocks)):
tag = ECB.encrypt(strxor(messageblocks[i], tag))
return hexlify(tag)
except:
print("\nYou can't sign that way! No padding done here boy!")
exit()

if __name__ == '__main__':

flag = "\nWhat? Nooooooooo!!! xiomara{1_b0w_d0wn_70_y0u!}"
key = b'YELLOW SUBMARINE'
print("\nYou wanna challenge me? You trying to break my signing scheme? LOLLLLLL ><")
print("Anyways, try hard for that boy!")
print("Press 0 to get your message signed and 1 to submit a forgery...Pffff! Seriously?")
while(True):
try:
inp = raw_input("\n")
if(inp=="0"):
hex_msg = raw_input("\nGimme your hex encoded message\n")
msg = unhexlify(hex_msg)
hex_tag = sign(key, msg)
print("\nThere you go! Here's my hex encoded tag!")
print(hex_tag)
else:
print("\nOh! So, you are up for it?")
print("\nAlright! Gimme just two different hex encoded messages that could sign to the same tag!")
msg1 = unhexlify(raw_input("\nMessage #1: \n"))
msg2 = unhexlify(raw_input("\nMessage #2: \n"))
if(msg1 == msg2):
print("\nI am not fool boy! Get back and do the job like a grown up!")
exit()
if(msg1 != msg2 and sign(key, msg1)==sign(key, msg2)):
print(flag)
exit()
else:
print("\nOops! They don't match! Told ya! Hard work my son...Better luck next time!")
exit()
except:
exit()

这道题目提供出来的代码也就是下面的关键签名代码:

1
2
3
4
5
6
7
8
9
10
11
def sign(key, message):
try:
ECB = AES.new(key, AES.MODE_ECB)
messageblocks = [message[i:i + 16] for i in range(0, len(message), 16)]
tag = ECB.encrypt(messageblocks[0])
for i in range(1,len(messageblocks)):
tag = ECB.encrypt(strxor(messageblocks[i], tag))
return hexlify(tag)
except:
print("\nYou can't sign that way! No padding done here boy!")
exit()

同样的原理,当明文分组P1为16字节,一个分组时,根据ECB加密模式的特点,MAC1 = Encrypt(P1) = C1

当明文分组P1‘为两个分组且P1’ = P1 + P2时,MAC2 = Encrypt(P2 xor MAC1) = Encrypt(P2 xor C1)

于是当MAC1 = MAC2时,得到 P2 = P1 xor C1

最终的代码为:

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
#encoding=utf-8

from pwn import *
from Crypto.Util.strxor import strxor

s1 = "a"*16
s1hex = s1.encode("hex")

l = remote("127.0.0.1",18000)

l.recv()
l.sendline("0")
l.recv()
l.sendline(s1hex)
l.recvline()
l.recvline()

y = l.recvline().strip()
print y

i3 = strxor(s1,y.decode("hex"))
print i3

s2 = s1+i3
s2hex = s2.encode("hex")

l.recv()
l.sendline("1")
l.recv()
l.sendline(s1hex)
l.recv()
l.sendline(s2hex)
l.interactive()

得到flag为:

automne

CATALOG