pub fn xor(key: &[u8; 32], nonce: &[u8; 12], counter: u32, data: &mut [u8]) {
let mut block = [0u8; 64];
let mut ctr = counter;
for chunk in data.chunks_mut(64) {
keystream_block(key, nonce, ctr, &mut block);
for (b, k) in chunk.iter_mut().zip(block.iter()) {
*b ^= *k;
}
ctr = ctr.wrapping_add(1);
}
}
fn keystream_block(key: &[u8; 32], nonce: &[u8; 12], counter: u32, out: &mut [u8; 64]) {
let mut state = [0u32; 16];
state[0] = 0x6170_7865;
state[1] = 0x3320_646e;
state[2] = 0x7962_2d32;
state[3] = 0x6b20_6574;
for i in 0..8 {
state[4 + i] =
u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
}
state[12] = counter;
for i in 0..3 {
state[13 + i] = u32::from_le_bytes([
nonce[i * 4],
nonce[i * 4 + 1],
nonce[i * 4 + 2],
nonce[i * 4 + 3],
]);
}
let mut working = state;
for _ in 0..10 {
quarter_round(&mut working, 0, 4, 8, 12);
quarter_round(&mut working, 1, 5, 9, 13);
quarter_round(&mut working, 2, 6, 10, 14);
quarter_round(&mut working, 3, 7, 11, 15);
quarter_round(&mut working, 0, 5, 10, 15);
quarter_round(&mut working, 1, 6, 11, 12);
quarter_round(&mut working, 2, 7, 8, 13);
quarter_round(&mut working, 3, 4, 9, 14);
}
for i in 0..16 {
let word = working[i].wrapping_add(state[i]);
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes());
}
}
fn quarter_round(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
s[a] = s[a].wrapping_add(s[b]);
s[d] = (s[d] ^ s[a]).rotate_left(16);
s[c] = s[c].wrapping_add(s[d]);
s[b] = (s[b] ^ s[c]).rotate_left(12);
s[a] = s[a].wrapping_add(s[b]);
s[d] = (s[d] ^ s[a]).rotate_left(8);
s[c] = s[c].wrapping_add(s[d]);
s[b] = (s[b] ^ s[c]).rotate_left(7);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc8439_test_vector() {
let key: [u8; 32] = std::array::from_fn(|i| i as u8);
let nonce: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0x4a, 0, 0, 0, 0];
let 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.";
let mut buf = plaintext.to_vec();
xor(&key, &nonce, 1, &mut buf);
let expected_prefix = [
0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d,
0x69, 0x81,
];
assert_eq!(&buf[..16], &expected_prefix);
xor(&key, &nonce, 1, &mut buf);
assert_eq!(&buf, plaintext);
}
}