The Algorithms logo
The Algorithms
AboutDonate

Chacha

E
/*
 * ChaCha20 implementation based on RFC8439
 * ChaCha20 is a stream cipher developed independently by Daniel J. Bernstein.
 * To use it, the `chacha20` function should be called with appropriate
 * parameters and the output of the function should be XORed with plain text.
 */

macro_rules! quarter_round {
    ($a:expr,$b:expr,$c:expr,$d:expr) => {
        $a = $a.wrapping_add($b);
        $d = ($d ^ $a).rotate_left(16);
        $c = $c.wrapping_add($d);
        $b = ($b ^ $c).rotate_left(12);
        $a = $a.wrapping_add($b);
        $d = ($d ^ $a).rotate_left(8);
        $c = $c.wrapping_add($d);
        $b = ($b ^ $c).rotate_left(7);
    };
}

#[allow(dead_code)]
// "expand 32-byte k", written in little-endian order
pub const C: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];

/**
 * `chacha20` function takes as input an array of 16 32-bit integers (512 bits)
 * of which 128 bits is the constant 'expand 32-byte k', 256 bits is the key,
 * and 128 bits are nonce and counter. According to RFC8439, the nonce should
 * be 96 bits long, which leaves 32 bits for the counter. Given that the block
 * length is 512 bits, this leaves enough counter values to encrypt 256GB of
 * data.
 *
 * The 16 input numbers can be thought of as the elements of a 4x4 matrix like
 * the one bellow, on which we do the main operations of the cipher.
 *
 * +----+----+----+----+
 * | 00 | 01 | 02 | 03 |
 * +----+----+----+----+
 * | 04 | 05 | 06 | 07 |
 * +----+----+----+----+
 * | 08 | 09 | 10 | 11 |
 * +----+----+----+----+
 * | 12 | 13 | 14 | 15 |
 * +----+----+----+----+
 *
 * As per the diagram bellow, input[0, 1, 2, 3] are the constants mentioned
 * above, input[4..=11] is filled with the key, and input[6..=9] should be
 * filled with nonce and counter values. The output of the function is stored
 * in `output` variable and can be XORed with the plain text to produce the
 * cipher text.
 *
 * +------+------+------+------+
 * |      |      |      |      |
 * | C[0] | C[1] | C[2] | C[3] |
 * |      |      |      |      |
 * +------+------+------+------+
 * |      |      |      |      |
 * | key0 | key1 | key2 | key3 |
 * |      |      |      |      |
 * +------+------+------+------+
 * |      |      |      |      |
 * | key4 | key5 | key6 | key7 |
 * |      |      |      |      |
 * +------+------+------+------+
 * |      |      |      |      |
 * | ctr0 | no.0 | no.1 | no.2 |
 * |      |      |      |      |
 * +------+------+------+------+
 *
 * Note that the constants, the key, and the nonce should be written in
 * little-endian order, meaning that for example if the key is 01:02:03:04
 * (in hex), it corresponds to the integer 0x04030201. It is important to
 * know that the hex value of the counter is meaningless, and only its integer
 * value matters, and it should start with (for example) 0x00000000, and then
 * 0x00000001 and so on until 0xffffffff. Keep in mind that as soon as we get
 * from bytes to words, we stop caring about their representation in memory,
 * and we only need the math to be correct.
 *
 * The output of the function can be used without any change, as long as the
 * plain text has the same endianness. For example if the plain text is
 * "hello world", and the first word of the output is 0x01020304, then the
 * first byte of plain text ('h') should be XORed with the least-significant
 * byte of 0x01020304, which is 0x04.
*/
pub fn chacha20(input: &[u32; 16], output: &mut [u32; 16]) {
    output.copy_from_slice(&input[..]);
    for _ in 0..10 {
        // Odd round (column round)
        quarter_round!(output[0], output[4], output[8], output[12]); //  column 1
        quarter_round!(output[1], output[5], output[9], output[13]); //  column 2
        quarter_round!(output[2], output[6], output[10], output[14]); // column 3
        quarter_round!(output[3], output[7], output[11], output[15]); // column 4

        // Even round (diagonal round)
        quarter_round!(output[0], output[5], output[10], output[15]); // diag 1
        quarter_round!(output[1], output[6], output[11], output[12]); // diag 2
        quarter_round!(output[2], output[7], output[8], output[13]); //  diag 3
        quarter_round!(output[3], output[4], output[9], output[14]); //  diag 4
    }
    for (a, &b) in output.iter_mut().zip(input.iter()) {
        *a = a.wrapping_add(b);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fmt::Write;

    fn output_hex(inp: &[u32; 16]) -> String {
        let mut res = String::new();
        res.reserve(512 / 4);
        for &x in inp {
            write!(&mut res, "{x:08x}").unwrap();
        }
        res
    }

    #[test]
    // test vector 1
    fn basic_tv1() {
        let mut inp = [0u32; 16];
        let mut out = [0u32; 16];
        inp[0] = C[0];
        inp[1] = C[1];
        inp[2] = C[2];
        inp[3] = C[3];
        inp[4] = 0x03020100; // The key is 00:01:02:..:1f (hex)
        inp[5] = 0x07060504;
        inp[6] = 0x0b0a0908;
        inp[7] = 0x0f0e0d0c;
        inp[8] = 0x13121110;
        inp[9] = 0x17161514;
        inp[10] = 0x1b1a1918;
        inp[11] = 0x1f1e1d1c;
        inp[12] = 0x00000001; // The value of counter is 1 (an integer). Nonce:
        inp[13] = 0x09000000; // 00:00:00:09
        inp[14] = 0x4a000000; // 00:00:00:4a
        inp[15] = 0x00000000; // 00:00:00:00
        chacha20(&inp, &mut out);
        assert_eq!(
            output_hex(&out),
            concat!(
                "e4e7f11015593bd11fdd0f50c47120a3c7f4d1c70368c0339aaa22044e6cd4c3",
                "466482d209aa9f0705d7c214a2028bd9d19c12b5b94e16dee883d0cb4e3c50a2"
            )
        );
    }
}