I’ve needed to write some Python code to handle encryption and decryption recently. This is a short post describing how I did it.
I’m interacting with a system which encrypts data in transit like so:
The encryption algorithm is AES with a 16 bit [sic] key. The cipher mode is CBC with PKCS5 padding. The initialisation vector is 16 bytes of 00.
Thus I need to handle 128-bit AES in CBC mode, and padding according to PKCS5. In addition, the key and encrypted data is all base64 encoded.
There are a number of cryptography packages for Python. The first I found was a blog post describing PyCrypto. My requirements include PKCS5 padding, which PyCrypto doesn’t seem to do. It also has it’s own implementations of the encryption algorithms.
The padding algorithm seems pretty simple. If I understand correctly, it’d go something like this in Python:
= lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
pad = lambda s : s[0:-ord(s[-1])] unpad
But I’ve got better things to do than convince myself that I’ve implemented it correctly.
Thankfully, there’s another options: Me Too Crypto. Unlike PyCrypto, M2Crypto is a Python wrapper around OpenSSL which is nice, as my code will be running under mod_wsgi embedded in Apache along with mod_ssl.
Alas, it has rather poor documentation. The best guide to using it I’ve seen
is the test code. I’m using the EVP interface, and found test_AES()
in the EVP tests invaluable.
Without further ado, the code:
from base64 import b64encode, b64decode
from M2Crypto.EVP import Cipher
= ['encryptor', 'decryptor']
__all__
=1
ENC=0
DEC
def build_cipher(key, iv, op=ENC):
""""""""
return Cipher(alg='aes_128_cbc', key=key, iv=iv, op=op)
def encryptor(key, iv=None):
""""""
# Decode the key and iv
= b64decode(key)
key if iv is None:
= '\0' * 16
iv else:
= b64decode(iv)
iv
# Return the encryption function
def encrypt(data):
= build_cipher(key, iv, ENC)
cipher = cipher.update(data)
v = v + cipher.final()
v del cipher
= b64encode(v)
v return v
return encrypt
def decryptor(key, iv=None):
""""""
# Decode the key and iv
= b64decode(key)
key if iv is None:
= '\0' * 16
iv else:
= b64decode(iv)
iv
# Return the decryption function
def decrypt(data):
= b64decode(data)
data = build_cipher(key, iv, DEC)
cipher = cipher.update(data)
v = v + cipher.final()
v del cipher
return v
return decrypt