Skip to content

Instantly share code, notes, and snippets.

Last active February 22, 2024 03:35
  • Star 6 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save bonsaiviking/5571001 to your computer and use it in GitHub Desktop.
A simple/simplistic implementation of AES in pure Python.
#My AES implementation
# By Daniel Miller
def xor(s1, s2):
return tuple(a^b for a,b in zip(s1, s2))
class AES(object):
class __metaclass__(type):
def __init__(cls, name, bases, classdict):
cls.Gmul = {}
for f in (0x02, 0x03, 0x0e, 0x0b, 0x0d, 0x09):
cls.Gmul[f] = tuple(cls.gmul(f, x) for x in range(0,0x100))
Rcon = ( 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a )
Sbox = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
Sbox_inv = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
def rot_word(word):
return word[1:] + word[:1]
def sub_word(word):
return (AES.Sbox[b] for b in word)
def key_schedule(self):
expanded = []
expanded.extend(map(ord, self.key))
for i in range(self.nk, self.nb * ( + 1)):
t = expanded[(i-1)*4:i*4]
if i % self.nk == 0:
t = xor( AES.sub_word( AES.rot_word(t) ), (AES.Rcon[i // self.nk],0,0,0) )
elif self.nk > 6 and i % self.nk == 4:
t = AES.sub_word(t)
expanded.extend( xor(t, expanded[(i-self.nk)*4:(i-self.nk+1)*4]))
return expanded
def add_round_key(self, rkey):
for i, b in enumerate(rkey):
self.state[i] ^= b
def sub_bytes(self):
for i, b in enumerate(self.state):
self.state[i] = AES.Sbox[b]
def inv_sub_bytes(self):
for i, b in enumerate(self.state):
self.state[i] = AES.Sbox_inv[b]
def shift_rows(self):
rows = []
for r in range(4):
rows.append( self.state[r::4] )
rows[r] = rows[r][r:] + rows[r][:r]
self.state = [ r[c] for c in range(4) for r in rows ]
def inv_shift_rows(self):
rows = []
for r in range(4):
rows.append( self.state[r::4] )
rows[r] = rows[r][4-r:] + rows[r][:4-r]
self.state = [ r[c] for c in range(4) for r in rows ]
def gmul(a, b):
p = 0
for c in range(8):
if b & 1:
p ^= a
a <<= 1
if a & 0x100:
a ^= 0x11b
b >>= 1
return p
def mix_columns(self):
ss = []
for c in range(4):
col = self.state[c*4:(c+1)*4]
AES.Gmul[0x02][col[0]] ^ AES.Gmul[0x03][col[1]] ^ col[2] ^ col[3] ,
col[0] ^ AES.Gmul[0x02][col[1]] ^ AES.Gmul[0x03][col[2]] ^ col[3] ,
col[0] ^ col[1] ^ AES.Gmul[0x02][col[2]] ^ AES.Gmul[0x03][col[3]],
AES.Gmul[0x03][col[0]] ^ col[1] ^ col[2] ^ AES.Gmul[0x02][col[3]],
self.state = ss
def inv_mix_columns(self):
ss = []
for c in range(4):
col = self.state[c*4:(c+1)*4]
AES.Gmul[0x0e][col[0]] ^ AES.Gmul[0x0b][col[1]] ^ AES.Gmul[0x0d][col[2]] ^ AES.Gmul[0x09][col[3]],
AES.Gmul[0x09][col[0]] ^ AES.Gmul[0x0e][col[1]] ^ AES.Gmul[0x0b][col[2]] ^ AES.Gmul[0x0d][col[3]],
AES.Gmul[0x0d][col[0]] ^ AES.Gmul[0x09][col[1]] ^ AES.Gmul[0x0e][col[2]] ^ AES.Gmul[0x0b][col[3]],
AES.Gmul[0x0b][col[0]] ^ AES.Gmul[0x0d][col[1]] ^ AES.Gmul[0x09][col[2]] ^ AES.Gmul[0x0e][col[3]],
self.state = ss
def cipher(self, block):
#print "round[ 0].input: {0}".format(block.encode('hex'))
n = self.nb * 4
self.state = map(ord, block)
keys = self.key_schedule()
#print "round[ 0].k_sch: {0}".format(keys[0:n].encode('hex'))
for r in range(1,
#print "round[{0}].start: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].s_box: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].s_row: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].m_col: {1}".format(r,self.state.encode('hex'))
k = keys[r*n:(r+1)*n]
#print "round[{0}].k_sch: {1}".format(r,k.encode('hex'))
#print "output: {0}".format(self.state.encode('hex'))
return "".join(map(chr, self.state))
def inv_cipher(self, block):
#print "round[ 0].iinput: {0}".format(block.encode('hex'))
n = self.nb * 4
self.state = map(ord, block)
keys = self.key_schedule()
k = keys[*n:(*n]
#print "round[ 0].ik_sch: {0}".format(k.encode('hex'))
for r in range(, 0, -1):
#print "round[{0}].istart: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].is_row: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].is_box: {1}".format(r,self.state.encode('hex'))
k = keys[r*n:(r+1)*n]
#print "round[{0}].ik_sch: {1}".format(r,k.encode('hex'))
#print "round[{0}].ik_add: {1}".format(r,self.state.encode('hex'))
#print "round[{0}].im_col: {1}".format(r,self.state.encode('hex'))
#print "output: {0}".format(self.state.encode('hex'))
return "".join(map(chr, self.state))
class AES_128(AES):
def __init__(self):
self.nb = 4 = 10
self.nk = 4
if __name__=="__main__":
key = "2b7e151628aed2a6abf7158809cf4f3c".decode('hex')
#key = "000102030405060708090a0b0c0d0e0f".decode('hex')
check = (
#("00112233445566778899aabbccddeeff", "69c4e0d86a7b0430d8cdb78070b4c55a"),
("6bc1bee22e409f96e93d7e117393172a", "3ad77bb40d7a3660a89ecaf32466ef97"),
("ae2d8a571e03ac9c9eb76fac45af8e51", "f5d3d58503b9699de785895a96fdbaaf"),
("30c81c46a35ce411e5fbc1191a0a52ef", "43b1cd7f598ece23881b00e3ed030688"),
("f69f2445df4f9b17ad2b417be66c3710", "7b0c785e27e8ad3f8223207104725dd4"),
crypt = AES_128()
crypt.key = key
for c in check:
p = c[0].decode('hex')
v = c[1].decode('hex')
t = crypt.cipher(p)
if t == v:
print "yay!"
print "{0} != {1}".format(t.encode('hex'), c[1])
t = crypt.inv_cipher(v)
if t == p:
print "yay!"
print "{0} != {1}".format(t.encode('hex'), c[1])
Copy link

DarthKakkadus commented Feb 11, 2018

is this it ? does this code end with the else statement in lines 217-218 or is there something that I am overlooking?

Copy link

xacrimon commented Mar 6, 2018

This is not meant for executing, it is meant for importing and using the AES class

Copy link

by any chance, do you have ported version of python3?

Copy link

kayakMike commented Jun 8, 2023

yeah, I really don't think much of this gist, its got some pretty weird stuff in the metaclass, which probably causes some python 2/3 issues. also, AES doesn't really make a great base class, you could determine if its AES 128, 192, and 256 by just measuring the size of an initialized key and setting the number of rounds appropriately. AES implies a block size of 128 bits.
basically its a ride on the fail boat. I think the OP was trying to look smart.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment