use chacha20poly1305::{
aead::{Aead, KeyInit},
XChaCha20Poly1305,
};
use hmac::{Hmac, Mac};
use sha2::Sha256;
pub type Key = chacha20poly1305::Key;
pub type XNonce = chacha20poly1305::XNonce;
pub const XNONCE_LEN: usize = 24;
pub fn derive_xnonce(plaintext: &[u8], key: &Key) -> XNonce {
let mac: [u8; 32] = <Hmac<Sha256> as Mac>::new_from_slice(key.as_ref())
.expect("HMAC construction never fails")
.chain_update(plaintext)
.finalize()
.into_bytes()
.into();
let mut nonce = XNonce::default();
nonce.copy_from_slice(&mac[..XNONCE_LEN]);
nonce
}
pub fn encrypt(plaintext: &[u8], key: &Key) -> Result<Vec<u8>, chacha20poly1305::Error> {
let nonce = derive_xnonce(plaintext, key);
let c = XChaCha20Poly1305::new(key);
let mut ciphertext = c.encrypt(&nonce, plaintext)?;
ciphertext.extend(nonce);
Ok(ciphertext)
}
pub fn decrypt(ciphertext: &[u8], key: &Key) -> Result<Vec<u8>, chacha20poly1305::Error> {
if ciphertext.len() < XNONCE_LEN {
return Err(chacha20poly1305::Error);
}
let nonce_ptr = ciphertext.len() - XNONCE_LEN;
let nonce = XNonce::from_slice(&ciphertext[nonce_ptr..]);
let c = XChaCha20Poly1305::new(key);
let plaintext = c.decrypt(nonce, &ciphertext[..nonce_ptr])?;
Ok(plaintext)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chacha_encryption() {
let key = Key::from([0u8; 32]);
let ciphertext = encrypt(b"hello world", &key).unwrap();
let plaintext = decrypt(&ciphertext, &key).unwrap();
assert_eq!(
hex::encode(&ciphertext),
concat!(
"eb3ed51c1a77277d8cac42", "c866e16e75184b30110bbc22cfd77030", "c2ea634c993f050482b4e6243224087f7c23bdd3c07ab1a4", ),
);
assert_eq!(&plaintext, b"hello world");
for i in 0..ciphertext.len() {
let mut manipulated = ciphertext.clone();
manipulated[i] = ciphertext[i].wrapping_add(1);
decrypt(&manipulated, &key).expect_err("authentication tag detects manipulation");
}
}
}