pub use chacha20::ChaCha20Legacy as ChaCha20;
use crate::Tag;
use aead::{
AeadCore, Error, KeyInit, KeySizeUser, Result, TagPosition,
array::typenum::{U8, U16, U32},
};
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
use poly1305::Poly1305;
use subtle::ConstantTimeEq;
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
pub type ChaChaKey = chacha20::Key;
pub type ChaChaNonce = chacha20::LegacyNonce;
#[derive(Clone)]
pub struct ChaCha20Poly1305 {
key: ChaChaKey,
}
impl KeySizeUser for ChaCha20Poly1305 {
type KeySize = U32;
}
impl KeyInit for ChaCha20Poly1305 {
#[inline]
fn new(key: &ChaChaKey) -> Self {
Self { key: *key }
}
}
impl AeadCore for ChaCha20Poly1305 {
type NonceSize = U8;
type TagSize = U16;
const TAG_POSITION: TagPosition = TagPosition::Postfix;
}
impl ChaCha20Poly1305 {
pub fn encrypt(&self, nonce: &ChaChaNonce, buffer: &mut [u8], aad_len: usize) -> Result<Tag> {
Cipher::new(&self.key, nonce).encrypt(buffer, aad_len)
}
pub fn decrypt(
&self,
nonce: &ChaChaNonce,
buffer: &mut [u8],
tag: Tag,
aad_len: usize,
) -> Result<()> {
Cipher::new(&self.key, nonce).decrypt(buffer, tag, aad_len)
}
}
impl Drop for ChaCha20Poly1305 {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.key.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for ChaCha20Poly1305 {}
struct Cipher {
cipher: ChaCha20,
mac: Poly1305,
}
impl Cipher {
pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self {
let mut cipher = ChaCha20::new(key, nonce);
let mut poly1305_key = poly1305::Key::default();
cipher.apply_keystream(&mut poly1305_key);
let mac = Poly1305::new(&poly1305_key);
cipher.seek(64);
Self { cipher, mac }
}
#[inline]
pub fn encrypt(mut self, buffer: &mut [u8], aad_len: usize) -> Result<Tag> {
if buffer.len() < aad_len {
return Err(Error);
}
self.cipher.apply_keystream(&mut buffer[aad_len..]);
Ok(self.mac.compute_unpadded(buffer))
}
#[inline]
pub fn decrypt(mut self, buffer: &mut [u8], tag: Tag, aad_len: usize) -> Result<()> {
if buffer.len() < aad_len {
return Err(Error);
}
let expected_tag = self.mac.compute_unpadded(buffer);
if expected_tag.ct_eq(&tag).into() {
self.cipher.apply_keystream(&mut buffer[aad_len..]);
Ok(())
} else {
Err(Error)
}
}
}
#[cfg(test)]
mod tests {
use super::{ChaCha20Poly1305, KeyInit};
use hex_literal::hex;
#[test]
fn test_vector() {
let key = hex!("379a8ca9e7e705763633213511e8d92eb148a46f1dd0045ec8164e5d23e456eb");
let nonce = hex!("0000000000000003");
let aad = hex!("5709db2d");
let plaintext = hex!("06050000000c7373682d7573657261757468de5949ab061f");
let ciphertext = hex!("6dcfb03be8a55e7f0220465672edd921489ea0171198e8a7");
let tag = hex!("3e82fe0a2db7128d58ef8d9047963ca3");
const AAD_LEN: usize = 4;
const PT_LEN: usize = 24;
assert_eq!(aad.len(), AAD_LEN);
assert_eq!(plaintext.len(), PT_LEN);
let cipher = ChaCha20Poly1305::new(key.as_ref());
let mut buffer = [0u8; AAD_LEN + PT_LEN];
let (a, p) = buffer.split_at_mut(AAD_LEN);
a.copy_from_slice(&aad);
p.copy_from_slice(&plaintext);
let actual_tag = cipher
.encrypt(nonce.as_ref(), &mut buffer, AAD_LEN)
.unwrap();
assert_eq!(&buffer[..AAD_LEN], aad);
assert_eq!(&buffer[AAD_LEN..], ciphertext);
assert_eq!(actual_tag, tag);
cipher
.decrypt(nonce.as_ref(), &mut buffer, actual_tag, AAD_LEN)
.unwrap();
assert_eq!(&buffer[..AAD_LEN], aad);
assert_eq!(&buffer[AAD_LEN..], plaintext);
}
}