Skip to content

Gimli

Gimli

Source

Implementation

from cryptopals_lib import intarray_to_bytes, bytes_to_intarray, shift_rotate_left, hex_to_bytes, asint32, int_to_bytes

def print_state(state):
	print("{:08x} {:08x} {:08x} {:08x}  ".format(state[0], state[1], state[2],  state[3]), end="")
	print("{:08x} {:08x} {:08x} {:08x}  ".format(state[4], state[5], state[6],  state[7]), end="")
	print("{:08x} {:08x} {:08x} {:08x}".format(state[8], state[9], state[10], state[11]))

def gimli(intarray, rounds=24):
	#Do rounds backwards since the round number is par of the constant
	for round_num in range(rounds, 0, -1):

		#For Each Column Do the Block info
		for column in range(4):
			#Get shifted Temp variables
			x = shift_rotate_left(intarray[    column], 24)
			y = shift_rotate_left(intarray[4 + column], 9)
			z =                   intarray[8 + column]

			#Do Operation and store the data
			intarray[8 + column] = x ^ asint32(z << 1) ^ asint32((y & z) << 2)
			intarray[4 + column] = y ^ x               ^ asint32((x | z) << 1)
			intarray[    column] = z ^ y               ^ asint32((x & y) << 3)


		if round_num & 3 == 0:
			#Swap values 0<->1 and 2<->3
			intarray[0], intarray[1] = intarray[1], intarray[0]
			intarray[2], intarray[3] = intarray[3], intarray[2]

			#Add constant to deified by round number
			intarray[0] ^= (0x9e377900 | round_num)

		elif round_num & 3 == 2:
			#Swap values 0<->2 and 1<->3
			intarray[0], intarray[2] = intarray[2], intarray[0]
			intarray[1], intarray[3] = intarray[3], intarray[1]

	return intarray

class Gimli_hash():
	def __init__(self):
		self.state_length = 12
		self.byte_rate = 16
		self.state = [0 for x in range(self.state_length)]

	def _finalize(self, message_length):
		#Calculate the index for the padding
		padding_loc = (message_length % self.byte_rate)
		int_index = padding_loc // (self.byte_rate // 4)
		byte_index = padding_loc % (self.byte_rate // 4)
		padding_int = 0x1F << (8*byte_index)

		#XOR the correct data
		self.state[int_index] ^= padding_int

		#Add Second bit of padding to the beginning of the 4th 32-bit integer
		self.state[3] ^= 0x80000000

	def hash(self, message, outputsize=32):
		block_num = 0

		for idx, byte in enumerate(message):
			#Calculate the index of the 32-bit int array
			block_num = idx // (self.byte_rate // 4)

			#When a full block is done call gimli algorithm
			if idx % self.byte_rate == 0 and block_num != 0:
				self.state = gimli(self.state)

			#Update Blocks in correct endian to the first 4 state blocks only
			self.state[block_num % 4] ^= (byte << 8 * (idx % 4))

		#Finalize Message with constants and padding info
		self._finalize(len(message))

		#Squeeze Blocks
		output = b''
		idx = 0
		block_length = min(outputsize, self.byte_rate)

		#Output Data
		while outputsize > 0:
			#Call Gimli
			#gimli is also called before any output data is copyed
			#This call was usually in the finalize block but made more sense here
			if (idx * 4) % self.byte_rate == 0:
				self.state = gimli(self.state)
				idx = 0

			#Copy 32bit integer to output
			output += int_to_bytes(self.state[idx], False)

			#Update loop variables
			idx +=1
			outputsize -= 4

		return output
		
	def hash_digest(self, message):
		return self.hash(message).hex()


if __name__ == '__main__':
	#Gimli Test Vector
	input0 = hex_to_bytes("000000009e3779ba3c6ef37adaa66d4678dde7241715611ab54cdb2e53845566f1bbcfc88ff34a5a2e2ac522cc624026")
	int_input = bytes_to_intarray(input0, 4, byte_order="big")

	output0 = gimli(int_input)
	print(intarray_to_bytes(output0, 4, byte_order="big").hex())
	#ba11c85a91bad119380ce880d24c2c683eceffea277a921c4f73a0bdda5a9cd884b673f034e52ff79e2bef49f41bb8d6
	#ba11c85a91bad119380ce880d24c2c683eceffea277a921c4f73a0bdda5a9cd884b673f034e52ff79e2bef49f41bb8d6

	#Gimli Hash Test Vectors
	input1 = hex_to_bytes("5468657265277320706c656e747920666f722074686520626f7468206f662075732c206d61792074686520626573742044776172662077696e2e")
	input2 = hex_to_bytes("496620616e796f6e652077617320746f2061736b20666f72206d79206f70696e696f6e2c2077686963682049206e6f74652074686579277265206e6f742c204927642073617920776520776572652074616b696e6720746865206c6f6e67207761792061726f756e642e")
	input3 = hex_to_bytes("537065616b20776f7264732077652063616e20616c6c20756e6465727374616e6421")
	input4 = hex_to_bytes("49742773207472756520796f7520646f6e277420736565206d616e792044776172662d776f6d656e2e20416e6420696e20666163742c20746865792061726520736f20616c696b6520696e20766f69636520616e6420617070656172616e63652c2074686174207468657920617265206f6674656e206d697374616b656e20666f722044776172662d6d656e2e20416e64207468697320696e207475726e2068617320676976656e207269736520746f207468652062656c696566207468617420746865726520617265206e6f2044776172662d776f6d656e2c20616e6420746861742044776172766573206a75737420737072696e67206f7574206f6620686f6c657320696e207468652067726f756e64212057686963682069732c206f6620636f757273652c207269646963756c6f75732e")
	input5 = b''
	hash = Gimli_hash()
	print(hash.hash_digest(input1))
	#4afb3ff784c7ad6943d49cf5da79facfa7c4434e1ce44f5dd4b28f91a84d22c8
	#4afb3ff784c7ad6943d49cf5da79facfa7c4434e1ce44f5dd4b28f91a84d22c8

	hash = Gimli_hash()
	print(hash.hash_digest(input2))
	#ba82a16a7b224c15bed8e8bdc88903a4006bc7beda78297d96029203ef08e07c
	#ba82a16a7b224c15bed8e8bdc88903a4006bc7beda78297d96029203ef08e07c

	hash = Gimli_hash()
	print(hash.hash_digest(input3))
	#8dd4d132059b72f8e8493f9afb86c6d86263e7439fc64cbb361fcbccf8b01267
	#8dd4d132059b72f8e8493f9afb86c6d86263e7439fc64cbb361fcbccf8b01267
	
	hash = Gimli_hash()
	print(hash.hash_digest(input4))
	#8887a5367d961d6734ee1a0d4aee09caca7fd6b606096ff69d8ce7b9a496cd2f
	#8887a5367d961d6734ee1a0d4aee09caca7fd6b606096ff69d8ce7b9a496cd2f
	
	hash = Gimli_hash()
	print(hash.hash_digest(input5))
	#b0634b2c0b082aedc5c0a2fe4ee3adcfc989ec05de6f00addb04b3aaac271f67
	#b0634b2c0b082aedc5c0a2fe4ee3adcfc989ec05de6f00addb04b3aaac271f67