I am here because I was looking into Blake which is a variant of ChaCha stream cipher. I guess we will start with ChaCha
first.
BLAKE is a cryptographic hash function based on Daniel J. Bernstein’s ChaCha stream cipher, but a permuted copy of the input block, XORed with round constants, is added before each ChaCha round.
ChaChar and Salsa20 Link to heading
According to Salsa20, Chacha is a variant of cipher called Salsa20.
Salsa20 and the closely related ChaCha are stream ciphers developed by Daniel J. Bernstein. Salsa20, the original cipher, was designed in 2005, then later submitted to the eSTREAM European Union cryptographic validation process by Bernstein. ChaCha is a modification of Salsa20 published in 2008
From RFC 5, key_stream
is calculated for each block
and encrypted_message
is calculated and summed up over the blocks.
chacha20_encrypt(key, counter, nonce, plaintext): for j = 0 upto floor(len(plaintext)/64)-1 key_stream = chacha20_block(key, counter+j, nonce) block = plaintext[(j*64)..(j*64+63)] encrypted_message += block ^ key_stream end if ((len(plaintext) % 64) != 0) j = floor(len(plaintext)/64) key_stream = chacha20_block(key, counter+j, nonce) block = plaintext[(j*64)..len(plaintext)-1] encrypted_message += (block^key_stream)[0..len(plaintext)%64] end return encrypted_message end
A simple (and stupid) implementation for the algorithm is given in the snippet. inner_block
implements the XAR (xor, add rotate) based on ChaCha
specifications.
Implementation Link to heading
import json
from base64 import b64encode
def xor_bytes(v1 ,v2):
x = [ ((a) ^ (b)) for (a,b) in zip(v1, v2) ]
return bytes(x)
CONSTANTS = b'expand 32-byte k'
round_idx = [(0, 4, 8, 12),
(1, 5, 9, 13),
(2, 6, 10, 14),
(3, 7, 11, 15),
(0, 5, 10, 15),
(1, 6, 11, 12),
(2, 7, 8, 13),
(3, 4, 9, 14)]
def inner_block(state):
for a, b, c, d in round_idx:
xa = state[a]
xb = state[b]
xc = state[c]
xd = state[d]
xa = (xa + xb) & 0xffffffff
xd = xd ^ xa
xd = (xd << 16) & 0xffffffff | (xd >> 16)
xc = (xc + xd) & 0xffffffff
xb = xb ^ xc
xb = (xb << 12) & 0xffffffff | (xb >> 20)
xa = (xa + xb) & 0xffffffff
xd = xd ^ xa
xd = (xd << 8) & 0xffffffff | (xd >> 24)
xc = (xc + xd) & 0xffffffff
xb = xb ^ xc
xb = (xb << 7) & 0xffffffff | (xb >> 25)
state[a] = xa
state[b] = xb
state[c] = xc
state[d] = xd
def chacha20_block(key, nonce, counter):
key = [int.from_bytes(key[i:i+4], 'little') for i in range(0, len(key), 4)]
nonce = [int.from_bytes(nonce[i:i+4], 'little') for i in range(0, len(nonce), 4)]
CONSTANTS_SPLIT = [int.from_bytes(CONSTANTS[i:i+4], 'little') for i in range(0, len(CONSTANTS), 4)]
state = CONSTANTS_SPLIT + key + [counter] + nonce
initial_state = state[:]
for _ in range(10):
inner_block(state)
state = [x + y for x, y in zip(initial_state, state)]
state = [(x & 0xffffffff).to_bytes(4, 'little') for x in state]
state = b''.join(state)
return state
def chacha20_encrypt(key, nonce, plaintext, counter=1):
enc_msg = bytearray()
for j in range(0, (len(plaintext) // 64)):
block = plaintext[(j*64):(j*64+63)]
key_stream = chacha20_block(key, nonce, counter + j)
enc_msg += xor_bytes(block, key_stream)
print(f'key_stream0: {[hex(x) for x in key_stream]}')
if (len(plaintext) % 64) != 0:
j = len(plaintext) // 64
block = plaintext[(j*64):]
key_stream = chacha20_block(key, nonce, counter + j)
key_stream = key_stream[0:len(block)]
enc_msg += xor_bytes(block, key_stream)
print(f'key_stream1: {[hex(x) for x in key_stream]}')
return enc_msg
plaintext = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."
key = b'\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'
nonce = b'\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00'
x = chacha20_encrypt(key, nonce, plaintext)
h = b64encode(x).decode("utf-8")
print(f"nonce: {nonce} key: {key} hash {h}")
print(f'ct {[hex(x1) for x1 in x]}')
ct [‘0x6e’, ‘0x2e’, ‘0x35’, ‘0x9a’, ‘0x25’, ‘0x68’, ‘0xf9’, ‘0x80’, ‘0x41’, ‘0xba’, ‘0x7’, ‘0x28’, ‘0xdd’, ‘0xd’, ‘0x69’, ‘0x81’, ‘0xe9’, ‘0x7e’, ‘0x7a’, ‘0xec’, ‘0x1d’, ‘0x43’, ‘0x60’, ‘0xc2’, ‘0xa’, ‘0x27’, ‘0xaf’, ‘0xcc’, ‘0xfd’, ‘0x9f’, ‘0xae’, ‘0xb’, ‘0xf9’, ‘0x1b’, ‘0x65’, ‘0xc5’, ‘0x52’, ‘0x47’, ‘0x33’, ‘0xab’, ‘0x8f’, ‘0x59’, ‘0x3d’, ‘0xab’, ‘0xcd’, ‘0x62’, ‘0xb3’, ‘0x57’, ‘0x16’, ‘0x39’, ‘0xd6’, ‘0x24’, ‘0xe6’, ‘0x51’, ‘0x52’, ‘0xab’, ‘0x8f’, ‘0x53’, ‘0xc’, ‘0x35’, ‘0x9f’, ‘0x8’, ‘0x61’, ‘0x7’, ‘0xca’, ‘0xd’, ‘0xbf’, ‘0x50’, ‘0xd’, ‘0x6a’, ‘0x61’, ‘0x56’, ‘0xa3’, ‘0x8e’, ‘0x8’, ‘0x8a’, ‘0x22’, ‘0xb6’, ‘0x5e’, ‘0x52’, ‘0xbc’, ‘0x51’, ‘0x4d’, ‘0x16’, ‘0xcc’, ‘0xf8’, ‘0x6’, ‘0x81’, ‘0x8c’, ‘0xe9’, ‘0x1a’, ‘0xb7’, ‘0x79’, ‘0x37’, ‘0x36’, ‘0x5a’, ‘0xf9’, ‘0xb’, ‘0xbf’, ‘0x74’, ‘0xa3’, ‘0x5b’, ‘0xe6’, ‘0xb4’, ‘0xb’, ‘0x8e’, ‘0xed’, ‘0xf2’, ‘0x78’, ‘0x5e’, ‘0x42’, ‘0x87’, ‘0x4d’]