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.

alt text

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’]