|
| 1 | +/* |
| 2 | + * ChaCha20 implementation based on RFC8439 |
| 3 | + * ChaCha20 is a stream cipher developed independently by Daniel J. Bernstein. |
| 4 | + * To use it, the `chacha20` function should be called with appropriate |
| 5 | + * parameters and the output of the function should be XORed with plain text. |
| 6 | + */ |
| 7 | + |
| 8 | +macro_rules! quarter_round { |
| 9 | + ($a:expr,$b:expr,$c:expr,$d:expr) => { |
| 10 | + $a = $a.wrapping_add($b); |
| 11 | + $d = ($d ^ $a).rotate_left(16); |
| 12 | + $c = $c.wrapping_add($d); |
| 13 | + $b = ($b ^ $c).rotate_left(12); |
| 14 | + $a = $a.wrapping_add($b); |
| 15 | + $d = ($d ^ $a).rotate_left(8); |
| 16 | + $c = $c.wrapping_add($d); |
| 17 | + $b = ($b ^ $c).rotate_left(7); |
| 18 | + }; |
| 19 | +} |
| 20 | + |
| 21 | +#[allow(dead_code)] |
| 22 | +// "expand 32-byte k", written in little-endian order |
| 23 | +pub const C: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; |
| 24 | + |
| 25 | +/** |
| 26 | + * `chacha20` function takes as input an array of 16 32-bit integers (512 bits) |
| 27 | + * of which 128 bits is the constant 'expand 32-byte k', 256 bits is the key, |
| 28 | + * and 128 bits are nonce and counter. According to RFC8439, the nonce should |
| 29 | + * be 96 bits long, which leaves 32 bits for the counter. Given that the block |
| 30 | + * length is 512 bits, this leaves enough counter values to encrypt 256GB of |
| 31 | + * data. |
| 32 | + * |
| 33 | + * The 16 input numbers can be thought of as the elements of a 4x4 matrix like |
| 34 | + * the one bellow, on which we do the main operations of the cipher. |
| 35 | + * |
| 36 | + * +----+----+----+----+ |
| 37 | + * | 00 | 01 | 02 | 03 | |
| 38 | + * +----+----+----+----+ |
| 39 | + * | 04 | 05 | 06 | 07 | |
| 40 | + * +----+----+----+----+ |
| 41 | + * | 08 | 09 | 10 | 11 | |
| 42 | + * +----+----+----+----+ |
| 43 | + * | 12 | 13 | 14 | 15 | |
| 44 | + * +----+----+----+----+ |
| 45 | + * |
| 46 | + * As per the diagram bellow, input[0, 1, 2, 3] are the constants mentioned |
| 47 | + * above, input[4..=11] is filled with the key, and input[6..=9] should be |
| 48 | + * filled with nonce and counter values. The output of the function is stored |
| 49 | + * in `output` variable and can be XORed with the plain text to produce the |
| 50 | + * cipher text. |
| 51 | + * |
| 52 | + * +------+------+------+------+ |
| 53 | + * | | | | | |
| 54 | + * | C[0] | C[1] | C[2] | C[3] | |
| 55 | + * | | | | | |
| 56 | + * +------+------+------+------+ |
| 57 | + * | | | | | |
| 58 | + * | key0 | key1 | key2 | key3 | |
| 59 | + * | | | | | |
| 60 | + * +------+------+------+------+ |
| 61 | + * | | | | | |
| 62 | + * | key4 | key5 | key6 | key7 | |
| 63 | + * | | | | | |
| 64 | + * +------+------+------+------+ |
| 65 | + * | | | | | |
| 66 | + * | ctr0 | no.0 | no.1 | no.2 | |
| 67 | + * | | | | | |
| 68 | + * +------+------+------+------+ |
| 69 | + * |
| 70 | + * Note that the constants, the key, and the nonce should be written in |
| 71 | + * little-endian order, meaning that for example if the key is 01:02:03:04 |
| 72 | + * (in hex), it corresponds to the integer 0x04030201. It is important to |
| 73 | + * know that the hex value of the counter is meaningless, and only its integer |
| 74 | + * value matters, and it should start with (for example) 0x00000000, and then |
| 75 | + * 0x00000001 and so on until 0xffffffff. Keep in mind that as soon as we get |
| 76 | + * from bytes to words, we stop caring about their representation in memory, |
| 77 | + * and we only need the math to be correct. |
| 78 | + * |
| 79 | + * The output of the function can be used without any change, as long as the |
| 80 | + * plain text has the same endianness. For example if the plain text is |
| 81 | + * "hello world", and the first word of the output is 0x01020304, then the |
| 82 | + * first byte of plain text ('h') should be XORed with the least-significant |
| 83 | + * byte of 0x01020304, which is 0x04. |
| 84 | +*/ |
| 85 | +pub fn chacha20(input: &[u32; 16], output: &mut [u32; 16]) { |
| 86 | + output.copy_from_slice(&input[..]); |
| 87 | + for _ in 0..10 { |
| 88 | + // Odd round (column round) |
| 89 | + quarter_round!(output[0], output[4], output[8], output[12]); // column 1 |
| 90 | + quarter_round!(output[1], output[5], output[9], output[13]); // column 2 |
| 91 | + quarter_round!(output[2], output[6], output[10], output[14]); // column 3 |
| 92 | + quarter_round!(output[3], output[7], output[11], output[15]); // column 4 |
| 93 | + |
| 94 | + // Even round (diagonal round) |
| 95 | + quarter_round!(output[0], output[5], output[10], output[15]); // diag 1 |
| 96 | + quarter_round!(output[1], output[6], output[11], output[12]); // diag 2 |
| 97 | + quarter_round!(output[2], output[7], output[8], output[13]); // diag 3 |
| 98 | + quarter_round!(output[3], output[4], output[9], output[14]); // diag 4 |
| 99 | + } |
| 100 | + for (a, &b) in output.iter_mut().zip(input.iter()) { |
| 101 | + *a = a.wrapping_add(b); |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +#[cfg(test)] |
| 106 | +mod tests { |
| 107 | + use super::*; |
| 108 | + use std::fmt::Write; |
| 109 | + |
| 110 | + fn output_hex(inp: &[u32; 16]) -> String { |
| 111 | + let mut res = String::new(); |
| 112 | + res.reserve(512 / 4); |
| 113 | + for &x in inp { |
| 114 | + write!(&mut res, "{x:08x}").unwrap(); |
| 115 | + } |
| 116 | + res |
| 117 | + } |
| 118 | + |
| 119 | + #[test] |
| 120 | + // test vector 1 |
| 121 | + fn basic_tv1() { |
| 122 | + let mut inp = [0u32; 16]; |
| 123 | + let mut out = [0u32; 16]; |
| 124 | + inp[0] = C[0]; |
| 125 | + inp[1] = C[1]; |
| 126 | + inp[2] = C[2]; |
| 127 | + inp[3] = C[3]; |
| 128 | + inp[4] = 0x03020100; // The key is 00:01:02:..:1f (hex) |
| 129 | + inp[5] = 0x07060504; |
| 130 | + inp[6] = 0x0b0a0908; |
| 131 | + inp[7] = 0x0f0e0d0c; |
| 132 | + inp[8] = 0x13121110; |
| 133 | + inp[9] = 0x17161514; |
| 134 | + inp[10] = 0x1b1a1918; |
| 135 | + inp[11] = 0x1f1e1d1c; |
| 136 | + inp[12] = 0x00000001; // The value of counter is 1 (an integer). Nonce: |
| 137 | + inp[13] = 0x09000000; // 00:00:00:09 |
| 138 | + inp[14] = 0x4a000000; // 00:00:00:4a |
| 139 | + inp[15] = 0x00000000; // 00:00:00:00 |
| 140 | + chacha20(&inp, &mut out); |
| 141 | + assert_eq!( |
| 142 | + output_hex(&out), |
| 143 | + concat!( |
| 144 | + "e4e7f11015593bd11fdd0f50c47120a3c7f4d1c70368c0339aaa22044e6cd4c3", |
| 145 | + "466482d209aa9f0705d7c214a2028bd9d19c12b5b94e16dee883d0cb4e3c50a2" |
| 146 | + ) |
| 147 | + ); |
| 148 | + } |
| 149 | +} |
0 commit comments