const CONSTANTS: [u8; 16] = *b"expand 32-byte k";
#[inline]
fn load_u32_le(bytes: &[u8]) -> u32 {
let mut tmp = [0u8; 4];
tmp.copy_from_slice(bytes);
u32::from_le_bytes(tmp)
}
#[inline]
fn quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
let (mut xa, mut xb, mut xc, mut xd) = (state[a], state[b], state[c], state[d]);
xa = xa.wrapping_add(xb);
xd ^= xa;
xd = xd.rotate_left(16);
xc = xc.wrapping_add(xd);
xb ^= xc;
xb = xb.rotate_left(12);
xa = xa.wrapping_add(xb);
xd ^= xa;
xd = xd.rotate_left(8);
xc = xc.wrapping_add(xd);
xb ^= xc;
xb = xb.rotate_left(7);
(state[a], state[b], state[c], state[d]) = (xa, xb, xc, xd);
}
#[inline]
fn chacha20_block_words(state: &[u32; 16]) -> [u32; 16] {
let mut x = *state;
for _ in 0..10 {
quarter_round(&mut x, 0, 4, 8, 12);
quarter_round(&mut x, 1, 5, 9, 13);
quarter_round(&mut x, 2, 6, 10, 14);
quarter_round(&mut x, 3, 7, 11, 15);
quarter_round(&mut x, 0, 5, 10, 15);
quarter_round(&mut x, 1, 6, 11, 12);
quarter_round(&mut x, 2, 7, 8, 13);
quarter_round(&mut x, 3, 4, 9, 14);
}
for i in 0..16 {
x[i] = x[i].wrapping_add(state[i]);
}
x
}
#[inline]
fn chacha20_block_bytes(state: &[u32; 16]) -> [u8; 64] {
let words = chacha20_block_words(state);
let mut out = [0u8; 64];
for i in 0..16 {
out[4 * i..4 * i + 4].copy_from_slice(&words[i].to_le_bytes());
}
out
}
#[inline]
fn state_from_key_nonce(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u32; 16] {
[
load_u32_le(&CONSTANTS[0..4]),
load_u32_le(&CONSTANTS[4..8]),
load_u32_le(&CONSTANTS[8..12]),
load_u32_le(&CONSTANTS[12..16]),
load_u32_le(&key[0..4]),
load_u32_le(&key[4..8]),
load_u32_le(&key[8..12]),
load_u32_le(&key[12..16]),
load_u32_le(&key[16..20]),
load_u32_le(&key[20..24]),
load_u32_le(&key[24..28]),
load_u32_le(&key[28..32]),
counter,
load_u32_le(&nonce[0..4]),
load_u32_le(&nonce[4..8]),
load_u32_le(&nonce[8..12]),
]
}
#[inline]
fn hchacha20(key: &[u8; 32], nonce: &[u8; 16]) -> [u8; 32] {
let mut state = [
load_u32_le(&CONSTANTS[0..4]),
load_u32_le(&CONSTANTS[4..8]),
load_u32_le(&CONSTANTS[8..12]),
load_u32_le(&CONSTANTS[12..16]),
load_u32_le(&key[0..4]),
load_u32_le(&key[4..8]),
load_u32_le(&key[8..12]),
load_u32_le(&key[12..16]),
load_u32_le(&key[16..20]),
load_u32_le(&key[20..24]),
load_u32_le(&key[24..28]),
load_u32_le(&key[28..32]),
load_u32_le(&nonce[0..4]),
load_u32_le(&nonce[4..8]),
load_u32_le(&nonce[8..12]),
load_u32_le(&nonce[12..16]),
];
for _ in 0..10 {
quarter_round(&mut state, 0, 4, 8, 12);
quarter_round(&mut state, 1, 5, 9, 13);
quarter_round(&mut state, 2, 6, 10, 14);
quarter_round(&mut state, 3, 7, 11, 15);
quarter_round(&mut state, 0, 5, 10, 15);
quarter_round(&mut state, 1, 6, 11, 12);
quarter_round(&mut state, 2, 7, 8, 13);
quarter_round(&mut state, 3, 4, 9, 14);
}
let output = [
state[0], state[1], state[2], state[3], state[12], state[13], state[14], state[15],
];
let mut out = [0u8; 32];
for i in 0..8 {
out[4 * i..4 * i + 4].copy_from_slice(&output[i].to_le_bytes());
}
out
}
pub struct ChaCha20 {
state: [u32; 16],
block: [u8; 64],
offset: usize,
}
impl ChaCha20 {
#[must_use]
pub fn new(key: &[u8; 32], nonce: &[u8; 12]) -> Self {
Self::with_counter(key, nonce, 0)
}
#[must_use]
pub fn with_counter(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> Self {
Self {
state: state_from_key_nonce(key, nonce, counter),
block: [0u8; 64],
offset: 64,
}
}
pub fn new_wiping(key: &mut [u8; 32], nonce: &mut [u8; 12]) -> Self {
let out = Self::new(key, nonce);
crate::ct::zeroize_slice(key.as_mut_slice());
crate::ct::zeroize_slice(nonce.as_mut_slice());
out
}
#[inline]
fn refill(&mut self) {
self.block = chacha20_block_bytes(&self.state);
self.offset = 0;
self.state[12] = self.state[12].wrapping_add(1);
}
pub fn apply_keystream(&mut self, buf: &mut [u8]) {
let mut done = 0usize;
while done < buf.len() {
if self.offset == 64 {
self.refill();
}
let take = core::cmp::min(64 - self.offset, buf.len() - done);
for i in 0..take {
buf[done + i] ^= self.block[self.offset + i];
}
self.offset += take;
done += take;
}
}
pub fn fill(&mut self, buf: &mut [u8]) {
self.apply_keystream(buf);
}
pub fn keystream_block(&mut self) -> [u8; 64] {
let mut out = [0u8; 64];
self.apply_keystream(&mut out);
out
}
pub fn set_counter(&mut self, counter: u32) {
self.state[12] = counter;
crate::ct::zeroize_slice(self.block.as_mut_slice());
self.offset = 64;
}
}
impl Drop for ChaCha20 {
fn drop(&mut self) {
crate::ct::zeroize_slice(self.state.as_mut_slice());
crate::ct::zeroize_slice(self.block.as_mut_slice());
self.offset = 0;
}
}
pub struct XChaCha20 {
inner: ChaCha20,
}
impl XChaCha20 {
#[must_use]
pub fn new(key: &[u8; 32], nonce: &[u8; 24]) -> Self {
Self::with_counter(key, nonce, 0)
}
#[must_use]
pub fn with_counter(key: &[u8; 32], nonce: &[u8; 24], counter: u32) -> Self {
let mut prefix = [0u8; 16];
prefix.copy_from_slice(&nonce[..16]);
let mut subkey = hchacha20(key, &prefix);
let mut chacha_nonce = [0u8; 12];
chacha_nonce[4..].copy_from_slice(&nonce[16..]);
let inner = ChaCha20::with_counter(&subkey, &chacha_nonce, counter);
crate::ct::zeroize_slice(subkey.as_mut_slice());
Self { inner }
}
pub fn new_wiping(key: &mut [u8; 32], nonce: &mut [u8; 24]) -> Self {
let out = Self::new(key, nonce);
crate::ct::zeroize_slice(key.as_mut_slice());
crate::ct::zeroize_slice(nonce.as_mut_slice());
out
}
pub fn apply_keystream(&mut self, buf: &mut [u8]) {
self.inner.apply_keystream(buf);
}
pub fn fill(&mut self, buf: &mut [u8]) {
self.inner.fill(buf);
}
pub fn keystream_block(&mut self) -> [u8; 64] {
self.inner.keystream_block()
}
pub fn set_counter(&mut self, counter: u32) {
self.inner.set_counter(counter);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
use core::fmt::Write;
let _ = write!(&mut out, "{b:02x}");
}
out
}
#[test]
fn chacha20_rfc8439_block1_vector() {
let mut key = [0u8; 32];
for i in 0u8..32 {
key[usize::from(i)] = i;
}
let nonce = [
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
];
let mut c = ChaCha20::with_counter(&key, &nonce, 1);
let block = c.keystream_block();
assert_eq!(
hex(&block),
"10f1e7e4d13b5915500fdd1fa32071c4".to_owned()
+ "c7d1f4c733c068030422aa9ac3d46c4e"
+ "d2826446079faa0914c2d705d98b02a2"
+ "b5129cd1de164eb9cbd083e8a2503c4e"
);
}
#[test]
fn hchacha20_draft_vector() {
let mut key = [0u8; 32];
for i in 0u8..32 {
key[usize::from(i)] = i;
}
let nonce = [
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x31, 0x41,
0x59, 0x27,
];
let subkey = hchacha20(&key, &nonce);
assert_eq!(
hex(&subkey),
"82413b4227b27bfed30e42508a877d73".to_owned() + "a0f9e4d58a74a853c12ec41326d3ecdc"
);
}
#[test]
fn xchacha20_matches_hchacha20_plus_chacha20() {
let mut key = [0u8; 32];
for i in 0u8..32 {
key[usize::from(i)] = i.wrapping_mul(7);
}
let mut nonce = [0u8; 24];
for i in 0u8..24 {
nonce[usize::from(i)] = i.wrapping_mul(11);
}
let mut x = XChaCha20::with_counter(&key, &nonce, 5);
let mut x_stream = [0u8; 96];
x.fill(&mut x_stream);
let mut prefix = [0u8; 16];
prefix.copy_from_slice(&nonce[..16]);
let mut subkey = hchacha20(&key, &prefix);
let mut chacha_nonce = [0u8; 12];
chacha_nonce[4..].copy_from_slice(&nonce[16..]);
let mut c = ChaCha20::with_counter(&subkey, &chacha_nonce, 5);
let mut c_stream = [0u8; 96];
c.fill(&mut c_stream);
crate::ct::zeroize_slice(subkey.as_mut_slice());
assert_eq!(x_stream, c_stream);
}
#[test]
fn chacha20_roundtrip_xor() {
let key = [0x42u8; 32];
let nonce = [0x24u8; 12];
let msg = *b"chacha20 applies its stream directly to caller buffers....";
let mut enc = ChaCha20::new(&key, &nonce);
let mut ct = msg;
enc.apply_keystream(&mut ct);
let mut dec = ChaCha20::new(&key, &nonce);
dec.apply_keystream(&mut ct);
assert_eq!(ct, msg);
}
}