AES Tutorial / Python Implementation June 10th, 2007

There have been a number of comments and questions about whether this code is compliant or interoperable with other implementations. It is not. I wrote it as a non-crypto expert to teach myself more about AES. It was never intended to perform well or to be compatible with other AES implementations.

Update: 04-Apr-2009

I fixed two bugs in my AES implementation pointed out to me by Josiah Carlson. First, I was failing to pad properly files whose length was an even multiple of the block size. In those cases, bytes would be lost upon decrypting the file. Josiah also pointed out that I was using a static IV, which leaks information about messages which share common prefixes. This is a serious security bug and I was glad to have it pointed out.

Each row in the state table is shifted left by the number of bytes represented by the row number

Inverse operation simply shifts each row to the right by the number of bytes as the row number

# returns a copy of the word shifted n bytes (chars) positive
# values for n shift bytes left, negative values shift right
def rotate(word, n):
return word[n:]+word[0:n]
# iterate over each "virtual" row in the state table
# and shift the bytes to the LEFT by the appropriate
# offset
def shiftRows(state):
for i in range(4):
state[i*4:i*4+4] = rotate(state[i*4:i*4+4],i)

AES Operation – MixColumns

MixColumns is performed by multiplying each column (within the Galois finite field) by the following matrix:

The inverse operation is performed by multiplying each column by the following inverse matrix:

# every 32 bytes apply core schedule to temp
if size(expandedKey)%32 == 0temp = keyScheduleCore(temp, i)i → i + 1
# since 256-bit key -> add an extra sbox transformation to each new byte
for j in range(4):temp[j] = sbox[temp[j]]
# XOR temp with the 4-byte block 32 bytes before the end of the current expanded key.
# These 4 bytes become the next bytes in the expanded keyexpandedKey.append( temp XOR expandedKey[size(expandedKey)-32:size(expandedKey)-28]

Another function to note…

# returns a 16-byte round key based on an expanded key and round number
def createRoundKey(expandedKey, n):
return expandedKey[(n*16):(n*16+16)]

AES – Encrypting a Single Block

state → block of plaintext # 16 bytes of plaintext are copied into the state

expandedKey = expandKey(cipherKey) # create 240-bytes of key material to be used as round keys

roundNum → 0 # counter for which round number we are in

roundKey → createRoundKey(expandedKey, roundNum)

addRoundKey(state, roundKey) # each byte of state is XORed with the present roundKey

Question: suppose AES is used being used a stream cipher and the attacker knows the actual contents of the initial blocks (which is not difficult because, for example, most XMLs begin with DOCTYPE header). Can the attacker get the cipher text and derive the key from the known plaintext from the header?

Interesting, you have separate encrypt and decrypt functions, but they both invoke aesEncrypt. In fact in this implementation, since you are simply XORing plaintext with a key, the encryption is very malleable … I could easily change the ciphertext of “Help him” to “kill him” even if I can never decrypt it. I know it is not supposed to provide authentication, but still.

So I’m wondering why you chose to implement it like this. Usually people would implement it using one of the standard block cipher modes (http://en.wikipedia.org/wiki/Cipher_modes) to provide non-malleability.

Thanks for the comments, though I think you rushed through the reading of my code. First, the encrypt and decrypt functions do call separate methods, so I’m not sure where that comment came from or why it’s relevant. Your example of malleability is extremely contrived. Almost no crypto is going to withstand an attack if you have known plaintext and known ciphertexts, so I don’t think that’s an indictment of my code in particular. I would love to see an example of an 8 byte encrypted message, in plaintext and ciphertext, that can withstand this type of tampering. As far as your comment about not “XORing plaintext with a key”, I have not seen very many cryptosystems that don’t follow this pattern. All of the block cipher modes operate by generating blocks of key material and XORing those with blocks of plaintext to produce blocks of ciphertext. I don’t quite see how to avoid this pattern. Finally, my code does implement AES in Output Feedback Mode, so I am aware of block cipher modes, but I still don’t think these solve your example above. Maybe you can clarify your comments.

but when I use python 2 (like the original code) everything works fine.

My question as I’m newby in python and basically I’m testing some ciphers is, Do you know why this error ocurr and why is linked to the version, obiusly python3 is doing something different that i dont know and/or i cant tell.

Have you tried testing your variant with an AES implementation from a crypto library?
That is because when you call the function shiftRows you are actually shifting the state columns. When you call the mixColumns function you mix the rows.

Here are the correct definitions for shiftRows and mixColumns (and for their inverses): http://ideone.com/di7GBN
The rest should be kept the same.

I’ve been trying to validate your script against OpenSSL output using various permutations of options, but have been unable to create ciphertext that this script can decode (or vice versa) – particularly the -aes-256-ofb, -nosalt, and -iv 00 options (the latter involved hardcoding the IV in your code to all zeroes, for the purpose of testing).

I’m curious then: what AES implementation did you use to validate your script against? While I doubt I’m hitting a bug, it’s not out of the question and being able to compare this to a known implementation would be valuable.

I implemented your algorithm and discovered that in the encrypt and decrypt methods you only call aesEncrypt. aesDecrypt never seems to be called as far as I can tell. My implementation seems to be working fine and I am a bit puzzled about why the aesDecrypt method was included but not used. Thanks!