Skip to content

SipHash

SipHash

Example

import hashlib, binascii


hash(b'test')

Implementation

from cryptopals_lib import fixedlen_xor, bytes_to_intarray, asint64, asint, asint32, bytes_to_int, int_to_bytes
import os

class siphash():
	"""docstring for siphash"""
	def __init__(self, key):
		self.digest_size = 16
		self.block_size = 64

		self.input_length = 0
		self.first_input_64 = None
		
		#Check Key size
		assert len(key) == 16
		int_key0 = bytes_to_int(key[0:8], False)
		int_key1 = bytes_to_int(key[8:16], False)
		#print(int_key0, int_key1)

		#Set inital vectors with inital bytes with the key duplicated
		self.vectors = [
			(bytes_to_int(b"somepseu") ^ int_key0),
			(bytes_to_int(b"dorandom") ^ int_key1),
			(bytes_to_int(b"lygenera") ^ int_key0),
			(bytes_to_int(b"tedbytes") ^ int_key1),
		]
		#self._printVectors()

	def _sip_round(self, block):
		#Using a,b,c,d as 0,1,2,3 in the array
		temp_vectors = self.vectors

		#d ^ block
		temp_vectors[3] ^= block

		## 1 Round

		# (((b & 0x7ffffffffffff) << 13) | (b >> 51)) ^ (a + b)
		step_1 = asint64(temp_vectors[0] + temp_vectors[1])
		step_2 = ((asint(temp_vectors[1], 51) << 13) | (temp_vectors[1] >> 51)) ^ step_1
		step_3 = temp_vectors[2] + temp_vectors[3]
		step_4 = asint64(((temp_vectors[3] << 16) | (temp_vectors[3] >> 48)) ^ step_3)
		step_5 = asint64(step_2 + step_3)

		# print(f"e = {step_1}")
		# print(f"i = {step_2}")
		# print(f"f = {step_3}")
		# print(f"j = {step_4}")
		# print(f"h = {step_5}")
			
		#Set New Temp Vectors
		temp_vectors[0] = ((step_1 << 32) | (step_1 >> 32)) + step_4
		temp_vectors[1] = ((asint(step_2, 47) << 17) | (step_2 >> 47)) ^ step_5
		temp_vectors[3] = asint64(((step_4 << 21) | (step_4 >> 43)) ^ temp_vectors[0])

		#print(f"k = {temp_vectors[0]}")
		#print(f"l = {temp_vectors[1]}")
		#print(f"o = {temp_vectors[3]}")

		## 2nd Round
		step_1 = asint64(temp_vectors[0] + temp_vectors[1])
		step_2 = ((asint(temp_vectors[1], 51) << 13) | (temp_vectors[1] >> 51)) ^ step_1
		step_3 = ((step_5 << 32) | (step_5 >> 32)) + temp_vectors[3]
		step_4 = asint64(((temp_vectors[3] << 16) | (temp_vectors[3] >> 48)) ^ step_3)
		step_5 = asint64(step_2 + step_3)
		step_6 = asint64(((step_1 << 32) | (step_1 >> 32)) + step_4)

		#print(f"p = {step_1}")
		#print(f"q = {step_2}")
		#print(f"r = {step_3}")
		#print(f"s = {step_4}")
		#print(f"t = {step_5}")
		#print(f"u = {step_6}")

		#Set New Temp Vectors
		temp_vectors[0] = step_6 ^ block
		temp_vectors[1] = ((asint(step_2, 47) << 17) | (step_2 >> 47)) ^ step_5
		temp_vectors[2] = ((asint32(step_5) << 32)  | (step_5 >> 32))
		temp_vectors[3] = ((asint(step_4, 43) << 21) | (step_4 >> 43)) ^ step_6

		self.vectors = temp_vectors
		#return temp_vectors


		
	def _printVectors(self):
		print(f"V1: {hex(self.vectors[0])}")
		print(f"V2: {hex(self.vectors[1])}")
		print(f"V3: {hex(self.vectors[2])}")
		print(f"V4: {hex(self.vectors[3])}")

	def hash(self, data):
		#Split into 8 length chunks
		self.input_length = len(data)

		#Process the full block
		max_idx = (len(data)//8)*8
		#print(f"v: {self.vectors}")

		for idx in range(0, max_idx, 8):
			#Do SIP Round on block
			int_data = bytes_to_int(data[idx:idx+8], False)
			self._sip_round(int_data)
			#print(f"v: {self.vectors}")

		#Set Finalize Varables 
		self.input_64 = bytes_to_int(data[max_idx:], False)

		#Finalize a partial blocks
		return self._finalize()
			


	def _finalize(self):
		#Setup end data 
		end_data = ((self.input_length & 0xff) << 56)
		end_data |= self.input_64
		#print(f"v1: {self.vectors}")


		#Do SIP round with data end
		self._sip_round(end_data)
		#print(f"v2: {self.vectors}")


		#Finalize
		self.vectors[2] ^= 0xff
		#print(f"v3: {self.vectors}")

		#Do Double sipround with 0 data
		self._sip_round(0)
		self._sip_round(0)
		#print(f"v4: {self.vectors}")

		return int_to_bytes(self.vectors[0] ^ self.vectors[1] ^ self.vectors[2] ^ self.vectors[3], False)


if __name__ == '__main__':
	#Test Vectors
	key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" #os.urandom(16)
	input_data = ''.join(chr(i) for i in range(64)).encode('utf-8')

	test_vectors = ["310e0edd47db6f72", "fd67dc93c539f874", "5a4fa9d909806c0d", "2d7efbd796666785",
	"b7877127e09427cf", "8da699cd64557618", "cee3fe586e46c9cb", "37d1018bf50002ab",
	"6224939a79f5f593", "b0e4a90bdf82009e", "f3b9dd94c5bb5d7a", "a7ad6b22462fb3f4",
	"fbe50e86bc8f1e75", "903d84c02756ea14", "eef27a8e90ca23f7", "e545be4961ca29a1",
	"db9bc2577fcc2a3f", "9447be2cf5e99a69", "9cd38d96f0b3c14b", "bd6179a71dc96dbb",
	"98eea21af25cd6be", "c7673b2eb0cbf2d0", "883ea3e395675393", "c8ce5ccd8c030ca8",
	"94af49f6c650adb8", "eab8858ade92e1bc", "f315bb5bb835d817", "adcf6b0763612e2f",
	"a5c91da7acaa4dde", "716595876650a2a6", "28ef495c53a387ad", "42c341d8fa92d832",
	"ce7cf2722f512771", "e37859f94623f3a7", "381205bb1ab0e012", "ae97a10fd434e015",
	"b4a31508beff4d31", "81396229f0907902", "4d0cf49ee5d4dcca", "5c73336a76d8bf9a",
	"d0a704536ba93e0e", "925958fcd6420cad", "a915c29bc8067318", "952b79f3bc0aa6d4",
	"f21df2e41d4535f9", "87577519048f53a9", "10a56cf5dfcd9adb", "eb75095ccd986cd0",
	"51a9cb9ecba312e6", "96afadfc2ce666c7", "72fe52975a4364ee", "5a1645b276d592a1",
	"b274cb8ebf87870a", "6f9bb4203de7b381", "eaecb2a30b22a87f", "9924a43cc1315724",
	"bd838d3aafbf8db7", "0b1a2a3265d51aea", "135079a3231ce660", "932b2846e4d70666",
	"e1915f5cb1eca46c", "f325965ca16d629f", "575ff28e60381be5", "724506eb4c328a95"]

	for i in range(64):
		sip = siphash(key)
		out = sip.hash(input_data[:i])
		print(f"SipHash({input_data[:i].hex()})")
		assert out.hex() == test_vectors[i]
		print(f"{out.hex() == test_vectors[i]}: {out.hex()} == {test_vectors[i]} ")

	#sip = siphash(b'0123456789ABCDEF')
	#sip.vectors = [4925064773550298181, 2461839666708829781, 6579568090023412561, 3611922228250500171]
	#sip._sip_round(0x100000000000061)
	#print(f"v2: {sip.vectors}")

	#sip  = siphash(key)
	#output = sip.hash(b"\x00\x01\x02\x03\x04\x05\x06\x07")
	#print(f"sip({b"\x00\x01\x02\x03\x04\x05\x06\x07"}) = {output.hex()}")

	#Hash data