Blake2
Blake2¶
- Improved Version of Blake
- Not vulnerable to length extension because the final block compressed with the chacha function contains the final message length.
- Since adding any information into this would increase that number the previous hash would not match to the next compress round
Example:
import hashlib, binascii
blake2s_hash = hashlib.blake2s(b'hello').digest()
print("blake2s('hello') =", binascii.hexlify(blake2s_hash))
#blake2s('hello') = b'19213bacc58dee6dbde3ceb9a47cbb330b3d86f8cca8997eb00be456f140ca25'
blake2b_hash = hashlib.blake2b(b'hello').digest()
print("blake2b('hello') =", binascii.hexlify(blake2b_hash))
#blake2b('hello') = b'e4cfa39a3d37be31c59609e807970799caa68a19bfaa15135f165085e01d41a65ba1e1b146aeb6bd0092b49eac214c103ccfa3a365954bbbe52f74a2b3620c94'
Blake2s¶
- 256bit hash optimized for 32bit microprocessors
Blake2b¶
- 512bit hash optimized for 64bit microprocessors
Implementation¶
from cryptopals_lib import *
class Blake2(object):
def __init__(self, version=256, key=None, salt=None, personalization=None, output_size=None):
self.permutations = [
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
]
self.current_length = 0
self.xor_block = False
self.__select_version(version, output_size)
#Copy the Buffers in to the IV array
self.iv = self.buffers[:]
#Deal with key, salt and personalization if required
if key != None:
self.key = bytearray(key[:self.blocksize // 2])
self.buffers[0] ^= (0x01010000) | (len(self.key) << 8) | self.output_size
else:
self.key = key
self.buffers[0] ^= (0x01010000) | self.output_size
if salt != None:
self.salt = int(salt[:self.blocksize // 8])
self.buffers[4] ^= asint(self.salt, self.blocksize)
self.buffers[5] ^= asint((self.salt >> self.blocksize), self.blocksize)
if personalization != None:
self.personalization = int(personalization[:self.blocksize // 8])
self.buffers[6] ^= asint(self.personalization, self.blocksize)
self.buffers[7] ^= asint((self.personalization >> self.blocksize), self.blocksize)
def __select_version(self, version, output_size):
#Blake 2s
if version <= 256:
self.buffers = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,]
self.rotations = [16,12,8,7]
self.blocksize = 32
self.rounds = 10
if output_size == None:
self.output_size = version // self.blocksize * 8
else:
self.output_size = output_size
#Blake2b
else:
self.buffers = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,]
self.rotations = [32,24,16,63]
self.blocksize = 64
self.rounds = 12
if output_size == None:
self.output_size = version // self.blocksize * 8
else:
self.output_size = output_size
#raise ValueError("Invalid Blake2 Version {}".format(self.version))
def _set_message(self, message):
#Convert to bytes if not already
byte_message = bytearray(message)
#Set Final Length
self.final_length = len(message)
#Pad the data to a multable of the block size
while len(byte_message) == 0 or len(byte_message) % (self.blocksize * 2) != 0:
byte_message.append(0x00)
return byte_message
def _chacha_quarter_round(self, a, b, c, d, message, round_num, index):
#Calculate indexes from Permuation table and round_index and offset
message_index = self.permutations[round_num][index]
constant_index = self.permutations[round_num][index+1]
#Modified first part to include message and round xor
a = asint((a + b) + message[message_index], self.blocksize)
d = asint(d ^ a, self.blocksize)
d = asint(shift_rotate_right(d, self.rotations[0], self.blocksize), self.blocksize)
c = asint(c + d, self.blocksize)
b = asint(b ^ c, self.blocksize)
b = asint(shift_rotate_right(b, self.rotations[1], self.blocksize), self.blocksize)
#Modified first part to include message and round xor
a = asint((a + b) + message[constant_index], self.blocksize)
d = asint(d ^ a, self.blocksize)
d = asint(shift_rotate_right(d, self.rotations[2], self.blocksize), self.blocksize)
c = asint(d + c, self.blocksize)
b = asint(b ^ c, self.blocksize)
b = asint(shift_rotate_right(b, self.rotations[3], self.blocksize), self.blocksize)
return [a,b,c,d]
def _compress_chunk(self, chunk):
#Start the compress function
#Create the start of the temp chunks
temp_chunk = bytes_to_intarray(chunk, (self.blocksize //8), byte_order="little")
#print(f"message: {[hex(x) for x in temp_chunk]}")
#Start setting up the temp buffers
temp_buffers = self.buffers[:] + self.iv[:]
temp_buffers[12] ^= asint(self.current_length, self.blocksize)
temp_buffers[13] ^= asint(self.current_length >> self.blocksize, self.blocksize)
#Do not xor currentlength when it is the last block and there is more than one block
if self.xor_block:
temp_buffers[14] ^= (2 **(self.blocksize) -1)
'''
Resulting temp_buffers looks like this
|IV |IV |IV |IV |
|IV |IV |IV |IV |
|Const ^ Salt |Const ^ Salt |Const ^ Salt |Const ^ Salt |
|Const ^ len[0] |Const ^ len[0] |Const ^ len[1] |Const ^ len[1] |
'''
#print([hex(x) for x in temp_buffers], self.xor_block, hex(self.current_length))
#Do ChaCha rounds with modifications
for index in range(self.rounds):
#Do Each Column
temp_buffers[0], temp_buffers[4], temp_buffers[8], temp_buffers[12] = self._chacha_quarter_round(temp_buffers[0], temp_buffers[4], temp_buffers[8], temp_buffers[12], temp_chunk, index, 0)
temp_buffers[1], temp_buffers[5], temp_buffers[9], temp_buffers[13] = self._chacha_quarter_round(temp_buffers[1], temp_buffers[5], temp_buffers[9], temp_buffers[13], temp_chunk, index, 2)
temp_buffers[2], temp_buffers[6], temp_buffers[10], temp_buffers[14] = self._chacha_quarter_round(temp_buffers[2], temp_buffers[6], temp_buffers[10], temp_buffers[14], temp_chunk, index, 4)
temp_buffers[3], temp_buffers[7], temp_buffers[11], temp_buffers[15] = self._chacha_quarter_round(temp_buffers[3], temp_buffers[7], temp_buffers[11], temp_buffers[15], temp_chunk, index, 6)
#Do Each Diagonal
temp_buffers[0], temp_buffers[5], temp_buffers[10], temp_buffers[15] = self._chacha_quarter_round(temp_buffers[0], temp_buffers[5], temp_buffers[10], temp_buffers[15], temp_chunk, index, 8)
temp_buffers[1], temp_buffers[6], temp_buffers[11], temp_buffers[12] = self._chacha_quarter_round(temp_buffers[1], temp_buffers[6], temp_buffers[11], temp_buffers[12], temp_chunk, index, 10)
temp_buffers[2], temp_buffers[7], temp_buffers[8], temp_buffers[13] = self._chacha_quarter_round(temp_buffers[2], temp_buffers[7], temp_buffers[8], temp_buffers[13], temp_chunk, index, 12)
temp_buffers[3], temp_buffers[4], temp_buffers[9], temp_buffers[14] = self._chacha_quarter_round(temp_buffers[3], temp_buffers[4], temp_buffers[9], temp_buffers[14], temp_chunk, index, 14)
#print([hex(x) for x in temp_buffers])
#Update Buffers
for x in range(8):
self.buffers[x] ^= temp_buffers[x] ^ temp_buffers[x+8]
#print([hex(x) for x in self.buffers])
def hash(self, message):
#If has a key then set the first block to the key and add padding to the blocksize
if self.key != None:
padded_key = self.key[:]
for x in range((self.blocksize * 2) - len(padded_key)):
padded_key.append(0x00)
message = padded_key + message
#Setup message with padding and length data
byte_message = self._set_message(message)
#Opperate on each of the chunks
blocks = to_blocks(byte_message, (self.blocksize * 2))
for index, chunk in enumerate(blocks):
#Update the current_length
self.current_length += (len(chunk))
#Fix Edge Case for padding goes into the next block
if index == len(blocks) - 1:
#Last Block
self.xor_block = True
self.current_length = self.final_length
#Compress the message Chunk
self._compress_chunk(chunk)
#Convert Intagers to Byte string
output = b""
for x in self.buffers[:self.output_size // 8]:
output += (x).to_bytes((self.blocksize // 8), byteorder='little')
return output
def hash_digest(self, message):
return self.hash(message).hex()
if __name__ == '__main__':
"""
print("key = b''")
for x in [224, 256, 384, 512]:
test = Blake2(x, output_size=32)
print(f"BLAKE2s-{x}(\"\"): {test.hash_digest(b'test')}")
#BLAKE2s-224(""): 1fa1291e65248b37b3433475b2a0dd63d54a11ecc4e3e034e7bc1ef4
#BLAKE2s-256(""): 69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9
#BLAKE2s-384(""): b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100
#BLAKE2s-512(""): 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce
print("\nKey = b'test'")
for x in [224, 256, 384, 512]:
test = Blake2(x, b'test')
print(f"BLAKE2s-{x}(\"\"): {test.hash_digest(b'')}")
#BLAKE2s-224(""): 8032c4a9d7b92692af7100c65319d233abfdfed4f4cdc5de0e5006dc
#BLAKE2s-256(""): e97e5a6ee41f36c29634dcddadc6edc7352a950ec5cb7610058ff63ea7bc4b80
#BLAKE2s-384(""): d998f718982498f390fb3fab366f3f94eb35d0c22ce9f4b2cfde96eb171072d91f071d6617bce70a21967155ff49a8cc
#BLAKE2s-512(""): af007b40b85039c1ac7ca29c4a484e3a614a9fead502fdf5693733ec52d768bc8915b3700a04ae607866141eda16322c9b85b433ccc09f9abd2825c4c23b4f31
"""
test = Blake2(512, output_size=32)
print(f"32: {test.hash_digest(b'test')}")
test = Blake2(512, output_size=64)
print(f"64: {test.hash_digest(b'test')}")