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 };