hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

chacha20.ha (5563B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use crypto::cipher;
      6 use crypto::math::{rotl32, xor};
      7 use endian;
      8 use io;
      9 
     10 // Size of a Chacha key, in bytes.
     11 export def KEYSZ: size = 32;
     12 
     13 // Size of the XChacha20 nonce, in bytes.
     14 export def XNONCESZ: size = 24;
     15 
     16 // Size of the Chacha20 nonce, in bytes.
     17 export def NONCESZ: size = 12;
     18 
     19 // The block size of the Chacha cipher in bytes.
     20 export def BLOCKSZ: size = 64;
     21 
     22 def ROUNDS: size = 20;
     23 const magic: [4]u32 = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
     24 
     25 // A ChaCha20 or XChaCha20 [[crypto::cipher::xorstream]] depending on
     26 // intialisation.
     27 export type stream = struct {
     28 	cipher::xorstream,
     29 	state: [16]u32,
     30 	xorbuf: [BLOCKSZ]u8,
     31 	xorused: size,
     32 	rounds: size,
     33 };
     34 
     35 // Creates a ChaCha20 or XChaCha20 stream cipher. Must be initialized with
     36 // [[chacha20_init]] or [[xchacha20_init]] prior to use, and must be closed
     37 // with [[io::close]] after use to wipe sensitive data from memory.
     38 export fn chacha20() stream = {
     39 	return stream {
     40 		stream = &cipher::xorstream_vtable,
     41 		h = 0,
     42 		keybuf = &keybuf,
     43 		advance = &advance,
     44 		finish = &finish,
     45 		xorused = BLOCKSZ,
     46 		rounds = ROUNDS,
     47 		...
     48 	};
     49 };
     50 
     51 // Initialize a Chacha20 stream.
     52 export fn chacha20_init(
     53 	s: *stream,
     54 	h: io::handle,
     55 	key: []u8,
     56 	nonce: []u8
     57 ) void = {
     58 	assert(len(key) == KEYSZ);
     59 	assert(len(nonce) == NONCESZ);
     60 
     61 	s.h = h;
     62 
     63 	s.state[0] = magic[0];
     64 	s.state[1] = magic[1];
     65 	s.state[2] = magic[2];
     66 	s.state[3] = magic[3];
     67 	s.state[4] = endian::legetu32(key[0..4]);
     68 	s.state[5] = endian::legetu32(key[4..8]);
     69 	s.state[6] = endian::legetu32(key[8..12]);
     70 	s.state[7] = endian::legetu32(key[12..16]);
     71 	s.state[8] = endian::legetu32(key[16..20]);
     72 	s.state[9] = endian::legetu32(key[20..24]);
     73 	s.state[10] = endian::legetu32(key[24..28]);
     74 	s.state[11] = endian::legetu32(key[28..32]);
     75 	s.state[13] = endian::legetu32(nonce[0..4]);
     76 	s.state[14] = endian::legetu32(nonce[4..8]);
     77 	s.state[15] = endian::legetu32(nonce[8..12]);
     78 };
     79 
     80 // Initialise a XChacha20 stream.
     81 export fn xchacha20_init(
     82 	s: *stream,
     83 	h: io::handle,
     84 	key: []u8,
     85 	nonce: []u8
     86 ) void = {
     87 	assert(len(key) == KEYSZ);
     88 	assert(len(nonce) == XNONCESZ);
     89 
     90 	let dkey: [32]u8 = [0...];
     91 	hchacha20(&dkey, key, nonce[..16]);
     92 
     93 	let dnonce: [NONCESZ]u8 = [0...];
     94 	dnonce[4..] = nonce[16..];
     95 
     96 	chacha20_init(s, h, &dkey, dnonce);
     97 
     98 	bytes::zero(dkey);
     99 	bytes::zero(dnonce);
    100 };
    101 
    102 // Derives a new key from 'key' and 'nonce' as used during XChaCha20
    103 // initialization. This function may only be used for specific purposes
    104 // such as X25519 key derivation. Do not use if in doubt.
    105 export fn hchacha20(out: []u8, key: []u8, nonce: []u8) void = {
    106 	assert(len(out) == KEYSZ);
    107 	assert(len(key) == KEYSZ);
    108 	assert(len(nonce) == 16);
    109 
    110 	let state: [16]u32 = [0...];
    111 	defer bytes::zero((state: []u8: *[*]u8)[..BLOCKSZ]);
    112 
    113 	state[0] = magic[0];
    114 	state[1] = magic[1];
    115 	state[2] = magic[2];
    116 	state[3] = magic[3];
    117 	state[4] = endian::legetu32(key[0..4]);
    118 	state[5] = endian::legetu32(key[4..8]);
    119 	state[6] = endian::legetu32(key[8..12]);
    120 	state[7] = endian::legetu32(key[12..16]);
    121 	state[8] = endian::legetu32(key[16..20]);
    122 	state[9] = endian::legetu32(key[20..24]);
    123 	state[10] = endian::legetu32(key[24..28]);
    124 	state[11] = endian::legetu32(key[28..32]);
    125 	state[12] = endian::legetu32(nonce[0..4]);
    126 	state[13] = endian::legetu32(nonce[4..8]);
    127 	state[14] = endian::legetu32(nonce[8..12]);
    128 	state[15] = endian::legetu32(nonce[12..16]);
    129 
    130 	hblock(state[..], &state, 20);
    131 
    132 	endian::leputu32(out[0..4], state[0]);
    133 	endian::leputu32(out[4..8], state[1]);
    134 	endian::leputu32(out[8..12], state[2]);
    135 	endian::leputu32(out[12..16], state[3]);
    136 	endian::leputu32(out[16..20], state[12]);
    137 	endian::leputu32(out[20..24], state[13]);
    138 	endian::leputu32(out[24..28], state[14]);
    139 	endian::leputu32(out[28..32], state[15]);
    140 };
    141 
    142 // Advances the key stream to "seek" to a future state by 'counter' times
    143 // [[BLOCKSZ]].
    144 export fn setctr(s: *stream, counter: u32) void = {
    145 	s.state[12] = counter;
    146 
    147 	// enforce block generation
    148 	s.xorused = BLOCKSZ;
    149 };
    150 
    151 fn keybuf(s: *cipher::xorstream) []u8 = {
    152 	let s = s: *stream;
    153 
    154 	if (s.xorused >= BLOCKSZ) {
    155 		block((s.xorbuf: []u8: *[*]u32)[..16], &s.state,
    156 			s.rounds);
    157 		// TODO on big endian systems s.xorbuf values need to
    158 		// be converted from little endian.
    159 		s.state[12] += 1;
    160 		s.xorused = 0;
    161 	};
    162 
    163 	return s.xorbuf[s.xorused..];
    164 };
    165 
    166 fn advance(s: *cipher::xorstream, n: size) void = {
    167 	let s = s: *stream;
    168 	assert(n <= len(s.xorbuf));
    169 	s.xorused += n;
    170 };
    171 
    172 fn block(dest: []u32, state: *[16]u32, rounds: size) void = {
    173 	hblock(dest, state, rounds);
    174 
    175 	for (let i = 0z; i < 16; i += 1) {
    176 		dest[i] += state[i];
    177 	};
    178 };
    179 
    180 fn hblock(dest: []u32, state: *[16]u32, rounds: size) void = {
    181 	for (let i = 0z; i < 16; i += 1) {
    182 		dest[i] = state[i];
    183 	};
    184 
    185 	for (let i = 0z; i < rounds; i += 2) {
    186 		qr(&dest[0], &dest[4], &dest[8], &dest[12]);
    187 		qr(&dest[1], &dest[5], &dest[9], &dest[13]);
    188 		qr(&dest[2], &dest[6], &dest[10], &dest[14]);
    189 		qr(&dest[3], &dest[7], &dest[11], &dest[15]);
    190 
    191 		qr(&dest[0], &dest[5], &dest[10], &dest[15]);
    192 		qr(&dest[1], &dest[6], &dest[11], &dest[12]);
    193 		qr(&dest[2], &dest[7], &dest[8], &dest[13]);
    194 		qr(&dest[3], &dest[4], &dest[9], &dest[14]);
    195 	};
    196 };
    197 
    198 
    199 fn qr(a: *u32, b: *u32, c: *u32, d: *u32) void = {
    200 	*a += *b;
    201 	*d ^= *a;
    202 	*d = rotl32(*d, 16);
    203 
    204 	*c += *d;
    205 	*b ^= *c;
    206 	*b = rotl32(*b, 12);
    207 
    208 	*a += *b;
    209 	*d ^= *a;
    210 	*d = rotl32(*d, 8);
    211 
    212 	*c += *d;
    213 	*b ^= *c;
    214 	*b = rotl32(*b, 7);
    215 };
    216 
    217 fn finish(c: *cipher::xorstream) void = {
    218 	let s = c: *stream;
    219 	bytes::zero((s.state[..]: *[*]u8)[..BLOCKSZ]);
    220 	bytes::zero(s.xorbuf);
    221 };