use crate::{Error, Nonce, Result, Tag};
use chacha20::{ChaCha20, Key};
use cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek};
use poly1305::Poly1305;
use subtle::ConstantTimeEq;
const KEY_SIZE: usize = 32;
pub(crate) struct ChaCha20Poly1305 {
cipher: ChaCha20,
mac: Poly1305,
}
impl ChaCha20Poly1305 {
pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self> {
#[allow(clippy::arithmetic_side_effects)]
if key.len() != KEY_SIZE * 2 {
return Err(Error::KeySize);
}
let (k_2, _k_1) = key.split_at(KEY_SIZE);
let key = Key::from_slice(k_2);
let nonce = if nonce.is_empty() {
Nonce::default()
} else {
Nonce::try_from(nonce).map_err(|_| Error::IvSize)?
};
let mut cipher = ChaCha20::new(key, &nonce.into());
let mut poly1305_key = poly1305::Key::default();
cipher.apply_keystream(&mut poly1305_key);
let mac = Poly1305::new(&poly1305_key);
cipher.seek(64);
Ok(Self { cipher, mac })
}
#[inline]
pub fn encrypt(mut self, buffer: &mut [u8]) -> Tag {
self.cipher.apply_keystream(buffer);
self.mac.compute_unpadded(buffer).into()
}
#[inline]
pub fn decrypt(mut self, buffer: &mut [u8], tag: Tag) -> Result<()> {
let expected_tag = self.mac.compute_unpadded(buffer);
if expected_tag.ct_eq(&tag).into() {
self.cipher.apply_keystream(buffer);
Ok(())
} else {
Err(Error::Crypto)
}
}
}