, , , , , ,

This is going to be yet another Python-related post, this time on something that took a good few hours of problem solving with PyCrypto.
The first problem was that AES and other block ciphers only accept message and key sizes of a certain number or bytes, so both must be padded in some way. AES works on blocks of 128 bits (16 bytes = 128 bits). In Electronic Code Book (EBC) mode it works by encrypting each block against the key. In Cipher Block Chaining (CBC) mode AES will encrypt each block with a previous block, using the key for the first block(s). The latter should be the more secure against known plaintext attacks, plus EBC would potentially output the same bit pattern for each repeating set of characters in the plaintext – if that set of characters can be determined, so can the key and therefore the entire message could be decrypted by a third party.

Producing a set key length isn’t difficult, as we can simply take any password and always produce a SHA256 value. This is a known method of key hardening, but in this case I only used it for convenience.
Pass = raw_input('KEY: ')
encodedPass = hashlib.sha256(Pass).digest()

Another method of padding must be used for the text message, as SHA256 is non-reversible. The AES function only allows plaintext lengths that are multiples of 16 bytes, but each character is a byte, and the string functions can be used for rounding a message up to the nearst 16 characters.
message = raw_input('MESSAGE: ')
length = 16 - (len(message) % 16)
message += chr(length)*length

In the following section I’ve used ECB mode AES anyway. Because the algorithm shifts bits around, and not whole ASCII character bytes, the ciphertext is normally binary data that doesn’t translate well to ASCII, so the output is Base64 encoded:
EncryptData = AES.new(encodedPass, AES.MODE_ECB)
ciphertext = EncryptData.encrypt(message)
print base64.b64encode(ciphertext)


The first part worked okay, but I spent several hours ironing out bugs so the second part could decrypt ciphertext independently. The password used for encrypting the message will have the same SHA256 value as before, so therefore the key will be exactly the same.
Pass = raw_input('KEY: ')
encodedPass = hashlib.sha256(Pass).digest()
message = raw_input('CIPHERTEXT: ')
length = 16 - (len(message) % 16)
message += chr(length)*length

The padding sometimes causes the output to be appended with erroneous symbols, but the plaintext is still quite readable. If characters are missing, check that both encryption and decryption functions are using the same mode (CBC or EBC).
DecryptData = AES.new(encodedPass, AES.MODE_EBC)
plaintext = DecryptData.decrypt(base64.b64decode(message))
print plaintext


Let’s see how it looks when implemented as GUI handlers:



The source files are available here.