Here's how to do this task using the popular cryptography library. This code was adapted from their documentation. It uses the data, key, and IV that were initially given in the question.
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
from base64 import b64decode, b64encode
backend = default_backend()
padder = padding.PKCS7(128).padder()
unpadder = padding.PKCS7(128).unpadder()
data = b'demo'
data = padder.update(data) + padder.finalize()
key = b64decode('HJkPmTz+uY7wd0p1+w//DABgbvPq9/230RwEG2sJ9mo=')
iv = b64decode('AAAAAAAAAAAAAAAAAAAAAA==')
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
encryptor = cipher.encryptor()
ct = encryptor.update(data) + encryptor.finalize()
ct_out = b64encode(ct)
print(ct_out)
decryptor = cipher.decryptor()
plain = decryptor.update(ct) + decryptor.finalize()
plain = unpadder.update(plain) + unpadder.finalize()
print(plain)
output
b'W2FEImF2qrAjaJ/LV+bgQA=='
b'demo'
Just for fun, here's an implementation that requires no 3rd-party packages.
Normally, one wouldn't do this sort of thing, since the first rule of crypto is "Don't roll your own crypto!". But here's some AES code I wrote as part of the Cryptopals challenge. ;) It does AES ECB encoding by directly calling the standard OpenSSL library via ctypes, and then uses that to perform CBC encryption / decryption.
This code was developed and tested on a Linux system running Python 3.6.0, but it should also run on Windows. I assume it will also run correctly on OSX.
import os
from base64 import b64decode, b64encode
from ctypes import *
AES_MAXNR = 14
AES_BLOCK_SIZE = 16
DECODE = 0
ENCODE = 1
class AES_KEY(Structure):
_fields_ = [
("rd_key", c_long * 4 *(AES_MAXNR + 1)),
("rounds", c_int),
]
crypto = cdll.LoadLibrary("libeay32.dll" if os.name == "nt" else "libssl.so")
# Function prototypes
AES_set_encrypt_key = crypto.AES_set_encrypt_key
AES_set_encrypt_key.restype = c_int
# userKey, bits, key
AES_set_encrypt_key.argtypes = [c_char_p, c_int, POINTER(AES_KEY)]
AES_set_decrypt_key = crypto.AES_set_decrypt_key
AES_set_decrypt_key.restype = c_int
# userKey, bits, key
AES_set_decrypt_key.argtypes = [c_char_p, c_int, POINTER(AES_KEY)]
AES_ecb_encrypt = crypto.AES_ecb_encrypt
AES_ecb_encrypt.restype = None
#in, out, key, enc(1=encode, 0=decode)
AES_ecb_encrypt.argtypes = [c_char_p, c_char_p, POINTER(AES_KEY), c_int]
set_key = (AES_set_decrypt_key, AES_set_encrypt_key)
def set_aes_key(key, encode):
''' Create an AES encoding or decoding key '''
keylen = len(key)
valid = {16, 24, 32}
if keylen not in valid:
msg = f'Key length must be one of {valid}, not {keylen}'
raise ValueError(msg)
aes_key = AES_KEY()
rc = set_key[encode](c_char_p(key), keylen * 8, byref(aes_key))
if rc != 0:
# I don't think we can get here...
raise ValueError('Error generating AES key', rc)
return aes_key
def aes_ecb(block, aes_key, encode):
''' Encrypt or decrypt a single block '''
outbuff = create_string_buffer(AES_BLOCK_SIZE)
AES_ecb_encrypt(c_char_p(block), outbuff, byref(aes_key), encode)
return outbuff.raw
def PKCS7_pad(data):
padsize = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
return data + bytes([padsize]) * padsize
def PKCS7_unpad(data):
offset = data[-1]
return data[:-offset]
def xor_bytes(a, b):
size = len(a)
a = int.from_bytes(a, 'big')
b = int.from_bytes(b, 'big')
return (a ^ b).to_bytes(size, 'big')
def aes_cbc_encode(data, key, iv):
ekey = set_aes_key(key, ENCODE)
data = PKCS7_pad(data)
cipher = []
for block in zip(*[iter(data)] * AES_BLOCK_SIZE):
block = bytes(block)
coded = aes_ecb(xor_bytes(iv, block), ekey, ENCODE)
cipher.append(coded)
iv = coded
return b''.join(cipher)
def aes_cbc_decode(data, key, iv):
dkey = set_aes_key(key, DECODE)
plain = []
for block in zip(*[iter(data)] * AES_BLOCK_SIZE):
block = bytes(block)
decoded = aes_ecb(block, dkey, DECODE)
plain.append(xor_bytes(iv, decoded))
iv = block
plain[-1] = PKCS7_unpad(plain[-1])
return b''.join(plain)
# Test
data = b'demo'
key = b64decode('HJkPmTz+uY7wd0p1+w//DABgbvPq9/230RwEG2sJ9mo=')
iv = b64decode('AAAAAAAAAAAAAAAAAAAAAA==')
cipher = aes_cbc_encode(data, key, iv)
out = b64encode(cipher)
print(out)
plain = aes_cbc_decode(cipher, key, iv)
print(plain)
output
b'W2FEImF2qrAjaJ/LV+bgQA=='
b'demo'