AES 256 CTR Mode Hash Function

Proof of concept, do not use, just for theory!

With the recent news of SHA1 everybody should be switching to SHA2 or SHA3 by now. I tried turning AES 256 in CTR mode into a 256 bit hash function by XORing the encrypted outputs together. For example, splitting your message into 32 byte blocks and using it as the keys (m0, m1, …, mN):

hash(m) = [AES256(nonce || counter0, m0) || AES256(nonce || counter1, m0)]
XOR [AES256(nonce || counter2, m1) || AES256(nonce || counter3, m1)]
...
XOR [AES256(nonce || counterN, mN) || AES256(nonce || counterO, mN)]
('test', '8ea2b7ca516745bfeafc49904b496089')
-
('', '09975b45dc8ecebd1519328dbec1c54d66b7fa7a2a4b762368497f81d864819b')
('a', 'c458ace20dc6458d6e589dbf0e560cbcad5b482e5934a86b6e98202a6cd5a6d5')
('abc', '304dfabb945406b40d53160080d078a1a0e5f8330263f578725ecfbccbb2c0ad')
('cba', '76cdfb5e58e06b5bf191656986aa35e405d3f582f307ea3847fe2a48136bf34e')
('the quick brown fox jumps over the lazy dog', '205639b3097f25a1c3a7bee4a3a66c4c3786129dd92c57fdddafc81568ab66f5')
('the quick brown fox jumps over the lazy eog', '3a7a025b5eb99f93caffd09331786cf6a15e1cb5bf41ae68342ea6b2208cf539')
import sys

def subbytes(matrix):
	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
	]
	for i in range(0, len(matrix)):
		matrix[i] = sbox[matrix[i]]
	return matrix

def transform(matrix):
	t = []
	y = 0
	for x in range(0, 16):
		z = (x % 4)
		if ((x > 0) and (z == 0)):
			y += 1
		t.append(matrix[(z * 4) + y])
	return t

def shiftrows(matrix):
	val = matrix.pop(4)
	matrix.insert(7, val)
	# end row 1
	val = matrix.pop(8)
	matrix.insert(11, val)
	val = matrix.pop(8)
	matrix.insert(11, val)
	# end row 2
	val = matrix.pop(15)
	matrix.insert(12, val)
	# end row 3
	return matrix

def gmul(a, b):
	p = 0
	for c in range(0, 8):
		if ((b & 1) != 0):
			p = (p ^ a)
		hi_bit_set = (a & 0x80)
		a = ((a << 1) & 0xff)
		if (hi_bit_set != 0):
			a = (a ^ 0x1b)
		b = (b >> 1)
	return p

def mixcols(s):
	t = []
	for c in range(0, 16):
		t.append(0)
	for c in range(0, 4):
		t[c +  0] = (gmul(0x02, s[c + 0]) ^ gmul(0x03, s[c + 4]) ^ s[c + 8] ^ s[c + 12]);
		t[c +  4] = (s[c + 0] ^ gmul(0x02, s[c + 4]) ^ gmul(0x03, s[c + 8]) ^ s[c + 12]);
		t[c +  8] = (s[c + 0] ^ s[c + 4] ^ gmul(0x02, s[c + 8]) ^ gmul(0x03, s[c + 12]));
		t[c + 12] = (gmul(0x03, s[c + 0]) ^ s[c + 4] ^ s[c + 8] ^ gmul(0x02, s[c + 12]));
	return t

def addkey(matrix, keyval):
	for i in range(0, 16):
		matrix[i] = (matrix[i] ^ keyval[i])
	return matrix

def rcon(ind):
	c = 1
	if (ind == 0):
		return 0
	while (ind != 1):
		c = gmul(c, 2)
		ind -= 1
	return c

def keycore(subkey, i):
	val = subkey.pop(0)
	subkey.append(val)
	subkey = subbytes(subkey)
	subkey[0] = (subkey[0] ^ rcon(i))
	return subkey

def keyexp(matrix):
	c = 32
	i = 1
	t = [0, 0, 0, 0]
	while (c < 240):
		for a in range(0, 4):
			t[a] = matrix[a + c - 4]
		if ((c % 32) == 0):
			t = keycore(t, i)
			i += 1
		if ((c % 32) == 16):
			t = subbytes(t)
		for a in range(0, 4):
			if (c >= len(matrix)):
				matrix.append(0)
			matrix[c] = (matrix[c - 32] ^ t[a])
			c += 1
	return matrix

def hexout(matrix):
	o = ""
	for d in matrix:
		h = hex(d)
		h = str(h)
		h = h[2:]
		if (len(h) < 2):
			h = ("0" + h)
		o += h
	return o

def aescoree(msg, key):
	out = []
	
	t = []
	i = 0
	l = len(key)
	for x in range(0, 32):
		t.append(0)
		if (i < l):
			t[x] = ord(key[i])
			i += 1
	keyval = keyexp(t)
	
	i = 0
	l = len(msg)
	while (i < l):
		input = []
		for x in range(0, 16):
			input.append(0)
		for x in range(0, 16):
			if (i < l):
				input[x] = ord(msg[i])
				i += 1
		
		for r in range(0, 15):
			if (r == 0):
				input = addkey(input, keyval[r*16:])
			
			if (r > 0):
				input = subbytes(input)
				input = transform(input)
				input = shiftrows(input)
				
				if (r < 14):
					input = mixcols(input)
				
				input = transform(input)
			
			if ((r > 0) and (r < 15)):
				input = addkey(input, keyval[r*16:])
		
		for o in input:
			out.append(o)
	
	return out

def aesctr_hash(message):
	x = 0
	l = len(message)
	n = 0
	h = []
	c = "aesctrhash31337!"
	d = 0
	for e in c:
		d = ((d << 8) + ord(e))
	while ((x == 0) or (x < l)):
		u = ((d + n) & 0xffffffffffffffffffffffffffffffff); n += 1
		v = ((d + n) & 0xffffffffffffffffffffffffffffffff); n += 1
		r = ""
		s = ""
		while (u > 0):
			r = (chr(u & 0xff) + r)
			u = (u >> 8)
		while (v > 0):
			s = (chr(v & 0xff) + s)
			v = (v >> 8)
		m = message[x:x+32]
		a = aescoree(r, m)
		b = aescoree(s, m)
		t = (a + b)
		if (x == 0):
			h = t
		else:
			for i in range(0, 32):
				h[i] = (h[i] ^ t[i])
		x += 32
	print(message, hexout(h))
	return h

print("test", hexout(aescoree("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f")))
print("-")
aesctr_hash("")
aesctr_hash("a")
aesctr_hash("abc")
aesctr_hash("cba")
aesctr_hash("the quick brown fox jumps over the lazy dog")
aesctr_hash("the quick brown fox jumps over the lazy eog")

2 thoughts on “AES 256 CTR Mode Hash Function

  1. Hi,

    From the short description at the beginning of the article, it seems to be Merkle-Damgard based, but it lacks the finalization step, so the hash can be easily subject to length extension attack. You would want to add a finalization step, including the size of the data hashed.

    Regards.

    • Hey Yann,

      That was quick! Thank you for that tip, I remember now that SHA also does this at the end of their hashing function as well 🙂

      This is why one needs a degree in Math and Crypto before making these algorithms!

      – Jon C

Leave a reply to Yann Droneaud Cancel reply