0CTF 一道关于AES-CBC的密码题

2017/04/05 CTF

前段时间参与了0CTF的竞赛,其中有一道关于密码学的题目,学习到不少知识。

这道题目应用到的主要知识点是AES加密中的CBC模式,这一点在《密码编码学与网络安全》课程中提到,我想起了一个有意思的事情,在这里先分享一下:据说在我们刚上这门课程的时候,赵淦森教授在第一节课说,谁能上黑板写出AES的5种加密模式的英文缩写以及工作原理,这门课的平时成绩就为满分,遗憾的是我的这门课的老师不是赵淦森教授。囧

这里简单的说一下关于CBC的工作原理:

Cipher Block Chaining,CBC模式,这种模式下加密算法的输入是当前明文组和上一个密文组的异或,使用的密钥k是相同的,这就相当于将所有的明文组链接在一起。加密算法的每次输入与明文没有固定的关系,因此重复的分组经过加密后这种特性将会消除。

那么在0CTF的题目中,我们能够拿到服务器后台的源代码,如下:

#!/usr/bin/python -u

from Crypto.Cipher import AES
from hashlib import md5
from Crypto import Random
from signal import alarm

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]


class Scheme:
    def __init__(self,key):
        self.key = key

    def encrypt(self,raw):
        raw = pad(raw)
        raw = md5(raw).digest() + raw

        iv = Random.new().read(BS)
        cipher = AES.new(self.key,AES.MODE_CBC,iv)

        return ( iv + cipher.encrypt(raw) ).encode("hex")

    def decrypt(self,enc):
        enc = enc.decode("hex")

        iv = enc[:BS]
        enc = enc[BS:]

        cipher = AES.new(self.key,AES.MODE_CBC,iv)
        blob = cipher.decrypt(enc)

        checksum = blob[:BS]
        data = blob[BS:]

        if md5(data).digest() == checksum:
            return unpad(data)
        else:
            return

key = Random.new().read(BS)
scheme = Scheme(key)

flag = open("flag",'r').readline()
alarm(30)

print "Welcome to 0CTF encryption service!"
while True:
    print "Please [r]egister or [l]ogin"
    cmd = raw_input()

    if not cmd:
        break

    if cmd[0]=='r' :
        name = raw_input().strip()

        if(len(name) > 32):
            print "username too long!"
            break
        if pad(name) == pad("admin"):
            print "You cannot use this name!"
            break
        else:
            print "Here is your secret:"
            print scheme.encrypt(name)


    elif cmd[0]=='l':
        data = raw_input().strip()
        name = scheme.decrypt(data)

        if name == "admin":
            print "Welcome admin!"
            print flag
        else:
            print "Welcome %s!" % name
    else:
        print "Unknown cmd!"
        break

题目的大意是,我们可以输入两个指令,分别是r和l。其中r用于注册,输入用户名之后,服务器会返回一段密文SCR;而输入l后,服务器会要求输入一段字符串,并将其进行解密,如果解密的结果为admin,那么服务器就会返回flag。

我们先观察这里的加密函数Scheme.encrypt(self,raw),其中raw是明文。函数将明文进行填充至16位,然后进行md5的计算并拼接在一起,然后利用AES-CBC进行加密。下面的图式总结了这个函数的作用:

而解密函数Scheme.decrypt(self,enc)的原理则是先提取前16位的iv向量,然后对后面的数据进行AES-CBC解密。其中函数将检查解密出来的数据中,提取前16位作为data,后面的数据作为checksum,如果满足md5(data)==checksum则返回data。注意这里返回的结果将会是对应加密的内容,在返回前已经进行了去除填充的操作。下面的图式总结了这个函数的作用:

因为在上面的代码中,我们发现key和iv变量都是临时生成的,不过它们都是可以直接获得。在每一次与服务器交互的过程中,这两个量不影响我们的破解过程。这个问题转化为:如何构造密文,使得服务器能够将其解密得到明文“admin”。

我们首先需要注册一个用户,其中名字是md5[pad("admin")]+"admin",这个时候服务器将会返回一段数据给我们,这段数据的内容将会是这样:

当我们需要解密的时候,传入的数据这样构造:

这时就能得到flag,由于比赛用的数据是直接与服务器交互,下面我给出一份简短的代码可以在本地直接完成测试。

#!/usr/bin/python -u

from Crypto.Cipher import AES
from hashlib import md5
from Crypto import Random
from signal import alarm

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]


class Scheme:
    def __init__(self,key):
        self.key = key

    def encrypt(self,raw):
        raw = pad(raw)
        raw = md5(raw).digest() + raw

        iv = Random.new().read(BS)
        cipher = AES.new(self.key,AES.MODE_CBC,iv)

        return ( iv + cipher.encrypt(raw) ).encode("hex")

    def decrypt(self,enc):
        enc = enc.decode("hex")

        iv = enc[:BS]
        enc = enc[BS:]

        cipher = AES.new(self.key,AES.MODE_CBC,iv)
        blob = cipher.decrypt(enc)

        checksum = blob[:BS]
        data = blob[BS:]

        if md5(data).digest() == checksum:
            return unpad(data)
        else:
            return

key = Random.new().read(BS)
scheme = Scheme(key)

flag = "this is flag"
alarm(30)

name = md5(pad("admin")).digest()+"admin"
res=scheme.encrypt(name)
data = res[32:64]+res[64:]
name = scheme.decrypt(data)
if name == "admin":
    print flag

这是一道关于CBC模式的简单的密码题,攻击的方法属于“选择密文攻击”。这篇文章就讲到这里,谢谢大家。

Search

    Table of Contents