<filename>test_cbc.py
#!/usr/bin/python

# from google-ctf 2016
# https://wolf-spider.ctfcompetition.com/admin

import urllib
from Crypto.Hash import SHA
from Crypto.Cipher import AES
import os, sys
import urlparse

class Storagex:
    aes_key=None
    mac_key=None

key = 'k2'*8
mac = 'pppx'*8
Storage=Storagex()
Storage.aes_key=key
Storage.mac_key=mac

class CookieCutter:
    KEY_SIZE=16

    @staticmethod
    def encode(t):
        # because len(s) may not be a multiple of the key size, we need to pad it
        # https://tools.ietf.org/html/rfc2315#section-10.3 has a way of
        # clearly indicating how padding should be performed, and how it
        # should be removed.

        pad = (16 - (len(t) % 16))
        t += chr(pad) * pad 

        iv=os.urandom(16)

        algo = AES.new(Storage.aes_key,
            AES.MODE_CBC,
            IV=iv)
        crypttext = algo.encrypt(t)
        return (iv + crypttext)

    @staticmethod
    def decode(string):
        if len(string) < 16:
            return ValueError, "bad string"
        iv, string = string[:16], string[16:]

        algo = AES.new(Storage.aes_key,
            AES.MODE_CBC,
            IV=iv)
        plaintext = str(algo.decrypt(string))
        pad = ord(plaintext[-1])
        if pad > CookieCutter.KEY_SIZE:
            raise ValueError, "pad error - pad is %d" % (pad)

        expected = chr(pad) * pad
        piece = plaintext[-pad:]
        
        if piece != expected:
            raise ValueError, "pad error - pad is %d" % (ord(piece[0]))

        raw = plaintext[:-pad]
        # print "raw is %r" % raw

        return raw

    @staticmethod
    def make(dct):
        tcd = urllib.urlencode(dct)

        # Use RFC1321 to hash our data, so it can't be tampered with.
        h = SHA.new()
        h.update(Storage.mac_key)
        h.update(tcd)
        s = h.digest()

        coded = CookieCutter.encode(tcd)

        return s.encode('hex') + "." + coded.encode('hex')

    @staticmethod
    def unmake(st):
        pieces = st.split(".")
        if len(pieces) != 2:
            return None

        s = CookieCutter.decode(pieces[1].decode('hex'))
        if s == None:
            return None

        print "Decrypted to: %s"%s
        h = SHA.new()
        h.update(Storage.mac_key)
        h.update(s)
        f = h.hexdigest()

        if pieces[0] != f:
            # print "hash comparasion failed :("
            return None

        kv = urlparse.parse_qsl(s)
        ret = {}
        for k, v in kv:
            ret[k] = v
        return ret

test=CookieCutter()

if __name__=='__main__':
    if len(sys.argv)<2:
        print test.make({'username':'ahg8v94fmhcksjdfhxxpgros'})
    else:
        var_new=sys.argv[1].strip('\n')
        try:
            ret=test.unmake(var_new)
            if ret==None:
                ret='Nope'
        except ValueError, m:
            ret="padding error"
        print ret
<filename>test_cbc.py
#!/usr/bin/python

# from google-ctf 2016
# https://wolf-spider.ctfcompetition.com/admin

import urllib
from Crypto.Hash import SHA
from Crypto.Cipher import AES
import os, sys
import urlparse

class Storagex:
    aes_key=None
    mac_key=None

key = 'k2'*8
mac = 'pppx'*8
Storage=Storagex()
Storage.aes_key=key
Storage.mac_key=mac

class CookieCutter:
    KEY_SIZE=16

    @staticmethod
    def encode(t):
        # because len(s) may not be a multiple of the key size, we need to pad it
        # https://tools.ietf.org/html/rfc2315#section-10.3 has a way of
        # clearly indicating how padding should be performed, and how it
        # should be removed.

        pad = (16 - (len(t) % 16))
        t += chr(pad) * pad 

        iv=os.urandom(16)

        algo = AES.new(Storage.aes_key,
            AES.MODE_CBC,
            IV=iv)
        crypttext = algo.encrypt(t)
        return (iv + crypttext)

    @staticmethod
    def decode(string):
        if len(string) < 16:
            return ValueError, "bad string"
        iv, string = string[:16], string[16:]

        algo = AES.new(Storage.aes_key,
            AES.MODE_CBC,
            IV=iv)
        plaintext = str(algo.decrypt(string))
        pad = ord(plaintext[-1])
        if pad > CookieCutter.KEY_SIZE:
            raise ValueError, "pad error - pad is %d" % (pad)

        expected = chr(pad) * pad
        piece = plaintext[-pad:]
        
        if piece != expected:
            raise ValueError, "pad error - pad is %d" % (ord(piece[0]))

        raw = plaintext[:-pad]
        # print "raw is %r" % raw

        return raw

    @staticmethod
    def make(dct):
        tcd = urllib.urlencode(dct)

        # Use RFC1321 to hash our data, so it can't be tampered with.
        h = SHA.new()
        h.update(Storage.mac_key)
        h.update(tcd)
        s = h.digest()

        coded = CookieCutter.encode(tcd)

        return s.encode('hex') + "." + coded.encode('hex')

    @staticmethod
    def unmake(st):
        pieces = st.split(".")
        if len(pieces) != 2:
            return None

        s = CookieCutter.decode(pieces[1].decode('hex'))
        if s == None:
            return None

        print "Decrypted to: %s"%s
        h = SHA.new()
        h.update(Storage.mac_key)
        h.update(s)
        f = h.hexdigest()

        if pieces[0] != f:
            # print "hash comparasion failed :("
            return None

        kv = urlparse.parse_qsl(s)
        ret = {}
        for k, v in kv:
            ret[k] = v
        return ret

test=CookieCutter()

if __name__=='__main__':
    if len(sys.argv)<2:
        print test.make({'username':'ahg8v94fmhcksjdfhxxpgros'})
    else:
        var_new=sys.argv[1].strip('\n')
        try:
            ret=test.unmake(var_new)
            if ret==None:
                ret='Nope'
        except ValueError, m:
            ret="padding error"
        print ret
#!/usr/bin/python # from google-ctf 2016 # https://wolf-spider.ctfcompetition.com/admin # because len(s) may not be a multiple of the key size, we need to pad it # https://tools.ietf.org/html/rfc2315#section-10.3 has a way of # clearly indicating how padding should be performed, and how it # should be removed. # print "raw is %r" % raw # Use RFC1321 to hash our data, so it can't be tampered with. # print "hash comparasion failed :("