local Array = require(".crypto.util.array")
local Bit = require(".crypto.util.bit")

local XOR = Bit.bxor

local SBOX = {
	[0] = 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,
}

local ISBOX = {
	[0] = 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,
}

local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 }
local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4 }

local ETABLE = {
	[0] = 0x01,
	0x03,
	0x05,
	0x0F,
	0x11,
	0x33,
	0x55,
	0xFF,
	0x1A,
	0x2E,
	0x72,
	0x96,
	0xA1,
	0xF8,
	0x13,
	0x35,
	0x5F,
	0xE1,
	0x38,
	0x48,
	0xD8,
	0x73,
	0x95,
	0xA4,
	0xF7,
	0x02,
	0x06,
	0x0A,
	0x1E,
	0x22,
	0x66,
	0xAA,
	0xE5,
	0x34,
	0x5C,
	0xE4,
	0x37,
	0x59,
	0xEB,
	0x26,
	0x6A,
	0xBE,
	0xD9,
	0x70,
	0x90,
	0xAB,
	0xE6,
	0x31,
	0x53,
	0xF5,
	0x04,
	0x0C,
	0x14,
	0x3C,
	0x44,
	0xCC,
	0x4F,
	0xD1,
	0x68,
	0xB8,
	0xD3,
	0x6E,
	0xB2,
	0xCD,
	0x4C,
	0xD4,
	0x67,
	0xA9,
	0xE0,
	0x3B,
	0x4D,
	0xD7,
	0x62,
	0xA6,
	0xF1,
	0x08,
	0x18,
	0x28,
	0x78,
	0x88,
	0x83,
	0x9E,
	0xB9,
	0xD0,
	0x6B,
	0xBD,
	0xDC,
	0x7F,
	0x81,
	0x98,
	0xB3,
	0xCE,
	0x49,
	0xDB,
	0x76,
	0x9A,
	0xB5,
	0xC4,
	0x57,
	0xF9,
	0x10,
	0x30,
	0x50,
	0xF0,
	0x0B,
	0x1D,
	0x27,
	0x69,
	0xBB,
	0xD6,
	0x61,
	0xA3,
	0xFE,
	0x19,
	0x2B,
	0x7D,
	0x87,
	0x92,
	0xAD,
	0xEC,
	0x2F,
	0x71,
	0x93,
	0xAE,
	0xE9,
	0x20,
	0x60,
	0xA0,
	0xFB,
	0x16,
	0x3A,
	0x4E,
	0xD2,
	0x6D,
	0xB7,
	0xC2,
	0x5D,
	0xE7,
	0x32,
	0x56,
	0xFA,
	0x15,
	0x3F,
	0x41,
	0xC3,
	0x5E,
	0xE2,
	0x3D,
	0x47,
	0xC9,
	0x40,
	0xC0,
	0x5B,
	0xED,
	0x2C,
	0x74,
	0x9C,
	0xBF,
	0xDA,
	0x75,
	0x9F,
	0xBA,
	0xD5,
	0x64,
	0xAC,
	0xEF,
	0x2A,
	0x7E,
	0x82,
	0x9D,
	0xBC,
	0xDF,
	0x7A,
	0x8E,
	0x89,
	0x80,
	0x9B,
	0xB6,
	0xC1,
	0x58,
	0xE8,
	0x23,
	0x65,
	0xAF,
	0xEA,
	0x25,
	0x6F,
	0xB1,
	0xC8,
	0x43,
	0xC5,
	0x54,
	0xFC,
	0x1F,
	0x21,
	0x63,
	0xA5,
	0xF4,
	0x07,
	0x09,
	0x1B,
	0x2D,
	0x77,
	0x99,
	0xB0,
	0xCB,
	0x46,
	0xCA,
	0x45,
	0xCF,
	0x4A,
	0xDE,
	0x79,
	0x8B,
	0x86,
	0x91,
	0xA8,
	0xE3,
	0x3E,
	0x42,
	0xC6,
	0x51,
	0xF3,
	0x0E,
	0x12,
	0x36,
	0x5A,
	0xEE,
	0x29,
	0x7B,
	0x8D,
	0x8C,
	0x8F,
	0x8A,
	0x85,
	0x94,
	0xA7,
	0xF2,
	0x0D,
	0x17,
	0x39,
	0x4B,
	0xDD,
	0x7C,
	0x84,
	0x97,
	0xA2,
	0xFD,
	0x1C,
	0x24,
	0x6C,
	0xB4,
	0xC7,
	0x52,
	0xF6,
	0x01,
}

local LTABLE = {
	[0] = 0x00,
	0x00,
	0x19,
	0x01,
	0x32,
	0x02,
	0x1A,
	0xC6,
	0x4B,
	0xC7,
	0x1B,
	0x68,
	0x33,
	0xEE,
	0xDF,
	0x03,
	0x64,
	0x04,
	0xE0,
	0x0E,
	0x34,
	0x8D,
	0x81,
	0xEF,
	0x4C,
	0x71,
	0x08,
	0xC8,
	0xF8,
	0x69,
	0x1C,
	0xC1,
	0x7D,
	0xC2,
	0x1D,
	0xB5,
	0xF9,
	0xB9,
	0x27,
	0x6A,
	0x4D,
	0xE4,
	0xA6,
	0x72,
	0x9A,
	0xC9,
	0x09,
	0x78,
	0x65,
	0x2F,
	0x8A,
	0x05,
	0x21,
	0x0F,
	0xE1,
	0x24,
	0x12,
	0xF0,
	0x82,
	0x45,
	0x35,
	0x93,
	0xDA,
	0x8E,
	0x96,
	0x8F,
	0xDB,
	0xBD,
	0x36,
	0xD0,
	0xCE,
	0x94,
	0x13,
	0x5C,
	0xD2,
	0xF1,
	0x40,
	0x46,
	0x83,
	0x38,
	0x66,
	0xDD,
	0xFD,
	0x30,
	0xBF,
	0x06,
	0x8B,
	0x62,
	0xB3,
	0x25,
	0xE2,
	0x98,
	0x22,
	0x88,
	0x91,
	0x10,
	0x7E,
	0x6E,
	0x48,
	0xC3,
	0xA3,
	0xB6,
	0x1E,
	0x42,
	0x3A,
	0x6B,
	0x28,
	0x54,
	0xFA,
	0x85,
	0x3D,
	0xBA,
	0x2B,
	0x79,
	0x0A,
	0x15,
	0x9B,
	0x9F,
	0x5E,
	0xCA,
	0x4E,
	0xD4,
	0xAC,
	0xE5,
	0xF3,
	0x73,
	0xA7,
	0x57,
	0xAF,
	0x58,
	0xA8,
	0x50,
	0xF4,
	0xEA,
	0xD6,
	0x74,
	0x4F,
	0xAE,
	0xE9,
	0xD5,
	0xE7,
	0xE6,
	0xAD,
	0xE8,
	0x2C,
	0xD7,
	0x75,
	0x7A,
	0xEB,
	0x16,
	0x0B,
	0xF5,
	0x59,
	0xCB,
	0x5F,
	0xB0,
	0x9C,
	0xA9,
	0x51,
	0xA0,
	0x7F,
	0x0C,
	0xF6,
	0x6F,
	0x17,
	0xC4,
	0x49,
	0xEC,
	0xD8,
	0x43,
	0x1F,
	0x2D,
	0xA4,
	0x76,
	0x7B,
	0xB7,
	0xCC,
	0xBB,
	0x3E,
	0x5A,
	0xFB,
	0x60,
	0xB1,
	0x86,
	0x3B,
	0x52,
	0xA1,
	0x6C,
	0xAA,
	0x55,
	0x29,
	0x9D,
	0x97,
	0xB2,
	0x87,
	0x90,
	0x61,
	0xBE,
	0xDC,
	0xFC,
	0xBC,
	0x95,
	0xCF,
	0xCD,
	0x37,
	0x3F,
	0x5B,
	0xD1,
	0x53,
	0x39,
	0x84,
	0x3C,
	0x41,
	0xA2,
	0x6D,
	0x47,
	0x14,
	0x2A,
	0x9E,
	0x5D,
	0x56,
	0xF2,
	0xD3,
	0xAB,
	0x44,
	0x11,
	0x92,
	0xD9,
	0x23,
	0x20,
	0x2E,
	0x89,
	0xB4,
	0x7C,
	0xB8,
	0x26,
	0x77,
	0x99,
	0xE3,
	0xA5,
	0x67,
	0x4A,
	0xED,
	0xDE,
	0xC5,
	0x31,
	0xFE,
	0x18,
	0x0D,
	0x63,
	0x8C,
	0x80,
	0xC0,
	0xF7,
	0x70,
	0x07,
}

local MIXTABLE = {
	0x02,
	0x03,
	0x01,
	0x01,
	0x01,
	0x02,
	0x03,
	0x01,
	0x01,
	0x01,
	0x02,
	0x03,
	0x03,
	0x01,
	0x01,
	0x02,
}

local IMIXTABLE = {
	0x0E,
	0x0B,
	0x0D,
	0x09,
	0x09,
	0x0E,
	0x0B,
	0x0D,
	0x0D,
	0x09,
	0x0E,
	0x0B,
	0x0B,
	0x0D,
	0x09,
	0x0E,
}

local RCON = {
	[0] = 0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
	0x01,
	0x02,
	0x04,
	0x08,
	0x10,
	0x20,
	0x40,
	0x80,
	0x1b,
	0x36,
	0x6c,
	0xd8,
	0xab,
	0x4d,
	0x9a,
	0x2f,
	0x5e,
	0xbc,
	0x63,
	0xc6,
	0x97,
	0x35,
	0x6a,
	0xd4,
	0xb3,
	0x7d,
	0xfa,
	0xef,
	0xc5,
	0x91,
	0x39,
	0x72,
	0xe4,
	0xd3,
	0xbd,
	0x61,
	0xc2,
	0x9f,
	0x25,
	0x4a,
	0x94,
	0x33,
	0x66,
	0xcc,
	0x83,
	0x1d,
	0x3a,
	0x74,
	0xe8,
	0xcb,
	0x8d,
}

local GMUL = function(A, B)
	if A == 0x01 then
		return B
	end
	if B == 0x01 then
		return A
	end
	if A == 0x00 then
		return 0
	end
	if B == 0x00 then
		return 0
	end

	local LA = LTABLE[A]
	local LB = LTABLE[B]

	local sum = LA + LB
	if sum > 0xFF then
		sum = sum - 0xFF
	end

	return ETABLE[sum]
end

local byteSub = Array.substitute

local shiftRow = Array.permute

local mixCol = function(i, mix)
	local out = {}

	local a, b, c, d

	a = GMUL(i[1], mix[1])
	b = GMUL(i[2], mix[2])
	c = GMUL(i[3], mix[3])
	d = GMUL(i[4], mix[4])
	out[1] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[5])
	b = GMUL(i[2], mix[6])
	c = GMUL(i[3], mix[7])
	d = GMUL(i[4], mix[8])
	out[2] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[9])
	b = GMUL(i[2], mix[10])
	c = GMUL(i[3], mix[11])
	d = GMUL(i[4], mix[12])
	out[3] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[1], mix[13])
	b = GMUL(i[2], mix[14])
	c = GMUL(i[3], mix[15])
	d = GMUL(i[4], mix[16])
	out[4] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[5], mix[1])
	b = GMUL(i[6], mix[2])
	c = GMUL(i[7], mix[3])
	d = GMUL(i[8], mix[4])
	out[5] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[5])
	b = GMUL(i[6], mix[6])
	c = GMUL(i[7], mix[7])
	d = GMUL(i[8], mix[8])
	out[6] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[9])
	b = GMUL(i[6], mix[10])
	c = GMUL(i[7], mix[11])
	d = GMUL(i[8], mix[12])
	out[7] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[5], mix[13])
	b = GMUL(i[6], mix[14])
	c = GMUL(i[7], mix[15])
	d = GMUL(i[8], mix[16])
	out[8] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[9], mix[1])
	b = GMUL(i[10], mix[2])
	c = GMUL(i[11], mix[3])
	d = GMUL(i[12], mix[4])
	out[9] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[5])
	b = GMUL(i[10], mix[6])
	c = GMUL(i[11], mix[7])
	d = GMUL(i[12], mix[8])
	out[10] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[9])
	b = GMUL(i[10], mix[10])
	c = GMUL(i[11], mix[11])
	d = GMUL(i[12], mix[12])
	out[11] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[9], mix[13])
	b = GMUL(i[10], mix[14])
	c = GMUL(i[11], mix[15])
	d = GMUL(i[12], mix[16])
	out[12] = XOR(XOR(a, b), XOR(c, d))

	a = GMUL(i[13], mix[1])
	b = GMUL(i[14], mix[2])
	c = GMUL(i[15], mix[3])
	d = GMUL(i[16], mix[4])
	out[13] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[5])
	b = GMUL(i[14], mix[6])
	c = GMUL(i[15], mix[7])
	d = GMUL(i[16], mix[8])
	out[14] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[9])
	b = GMUL(i[14], mix[10])
	c = GMUL(i[15], mix[11])
	d = GMUL(i[16], mix[12])
	out[15] = XOR(XOR(a, b), XOR(c, d))
	a = GMUL(i[13], mix[13])
	b = GMUL(i[14], mix[14])
	c = GMUL(i[15], mix[15])
	d = GMUL(i[16], mix[16])
	out[16] = XOR(XOR(a, b), XOR(c, d))

	return out
end

local keyRound = function(key, round)
	local i = (round - 1) * 32
	local out = key

	out[33 + i] = XOR(key[1 + i], XOR(SBOX[key[30 + i]], RCON[round]))
	out[34 + i] = XOR(key[2 + i], SBOX[key[31 + i]])
	out[35 + i] = XOR(key[3 + i], SBOX[key[32 + i]])
	out[36 + i] = XOR(key[4 + i], SBOX[key[29 + i]])

	out[37 + i] = XOR(out[33 + i], key[5 + i])
	out[38 + i] = XOR(out[34 + i], key[6 + i])
	out[39 + i] = XOR(out[35 + i], key[7 + i])
	out[40 + i] = XOR(out[36 + i], key[8 + i])

	out[41 + i] = XOR(out[37 + i], key[9 + i])
	out[42 + i] = XOR(out[38 + i], key[10 + i])
	out[43 + i] = XOR(out[39 + i], key[11 + i])
	out[44 + i] = XOR(out[40 + i], key[12 + i])

	out[45 + i] = XOR(out[41 + i], key[13 + i])
	out[46 + i] = XOR(out[42 + i], key[14 + i])
	out[47 + i] = XOR(out[43 + i], key[15 + i])
	out[48 + i] = XOR(out[44 + i], key[16 + i])

	out[49 + i] = XOR(SBOX[out[45 + i]], key[17 + i])
	out[50 + i] = XOR(SBOX[out[46 + i]], key[18 + i])
	out[51 + i] = XOR(SBOX[out[47 + i]], key[19 + i])
	out[52 + i] = XOR(SBOX[out[48 + i]], key[20 + i])

	out[53 + i] = XOR(out[49 + i], key[21 + i])
	out[54 + i] = XOR(out[50 + i], key[22 + i])
	out[55 + i] = XOR(out[51 + i], key[23 + i])
	out[56 + i] = XOR(out[52 + i], key[24 + i])

	out[57 + i] = XOR(out[53 + i], key[25 + i])
	out[58 + i] = XOR(out[54 + i], key[26 + i])
	out[59 + i] = XOR(out[55 + i], key[27 + i])
	out[60 + i] = XOR(out[56 + i], key[28 + i])

	out[61 + i] = XOR(out[57 + i], key[29 + i])
	out[62 + i] = XOR(out[58 + i], key[30 + i])
	out[63 + i] = XOR(out[59 + i], key[31 + i])
	out[64 + i] = XOR(out[60 + i], key[32 + i])

	return out
end

local keyExpand = function(key)
	local bytes = Array.copy(key)

	for i = 1, 7 do
		keyRound(bytes, i)
	end

	local keys = {}

	keys[1] = Array.slice(bytes, 1, 16)
	keys[2] = Array.slice(bytes, 17, 32)
	keys[3] = Array.slice(bytes, 33, 48)
	keys[4] = Array.slice(bytes, 49, 64)
	keys[5] = Array.slice(bytes, 65, 80)
	keys[6] = Array.slice(bytes, 81, 96)
	keys[7] = Array.slice(bytes, 97, 112)
	keys[8] = Array.slice(bytes, 113, 128)
	keys[9] = Array.slice(bytes, 129, 144)
	keys[10] = Array.slice(bytes, 145, 160)
	keys[11] = Array.slice(bytes, 161, 176)
	keys[12] = Array.slice(bytes, 177, 192)
	keys[13] = Array.slice(bytes, 193, 208)
	keys[14] = Array.slice(bytes, 209, 224)
	keys[15] = Array.slice(bytes, 225, 240)

	return keys
end

local addKey = Array.XOR

local AES = {}

AES.blockSize = 16

AES.encrypt = function(key, block)
	local keySchedule = keyExpand(key)

	--round 0
	block = addKey(block, keySchedule[1])

	--round 1
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[2])

	--round 2
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[3])

	--round 3
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[4])

	--round 4
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[5])

	--round 5
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[6])

	--round 6
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[7])

	--round 7
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[8])

	--round 8
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[9])

	--round 9
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[10])

	--round 10
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[11])

	--round 11
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[12])

	--round 12
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[13])

	--round 13
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = mixCol(block, MIXTABLE)
	block = addKey(block, keySchedule[14])

	--round 14
	block = byteSub(block, SBOX)
	block = shiftRow(block, ROW_SHIFT)
	block = addKey(block, keySchedule[15])

	return block
end

AES.decrypt = function(key, block)
	local keySchedule = keyExpand(key)

	--round 0
	block = addKey(block, keySchedule[15])

	--round 1
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[14])
	block = mixCol(block, IMIXTABLE)

	--round 2
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[13])
	block = mixCol(block, IMIXTABLE)

	--round 3
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[12])
	block = mixCol(block, IMIXTABLE)

	--round 4
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[11])
	block = mixCol(block, IMIXTABLE)

	--round 5
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[10])
	block = mixCol(block, IMIXTABLE)

	--round 6
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[9])
	block = mixCol(block, IMIXTABLE)

	--round 7
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[8])
	block = mixCol(block, IMIXTABLE)

	--round 8
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[7])
	block = mixCol(block, IMIXTABLE)

	--round 9
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[6])
	block = mixCol(block, IMIXTABLE)

	--round 10
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[5])
	block = mixCol(block, IMIXTABLE)

	--round 11
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[4])
	block = mixCol(block, IMIXTABLE)

	--round 12
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[3])
	block = mixCol(block, IMIXTABLE)

	--round 13
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[2])
	block = mixCol(block, IMIXTABLE)

	--round 14
	block = shiftRow(block, IROW_SHIFT)
	block = byteSub(block, ISBOX)
	block = addKey(block, keySchedule[1])

	return block
end

return AES
