use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, OsRng},
XChaCha20Poly1305, XNonce as InternalXNonce,
};
use zeroize::Zeroize;
use crate::{CryptoError, Result};
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct XChaChaKey([u8; 32]);
impl XChaChaKey {
pub fn generate() -> Self {
let key = XChaCha20Poly1305::generate_key(&mut OsRng);
Self(key.into())
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 {
return Err(CryptoError::InvalidKeyLength {
expected: 32,
actual: bytes.len(),
});
}
let mut key = [0u8; 32];
key.copy_from_slice(bytes);
Ok(Self(key))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone, Copy)]
pub struct XChaChaNonce([u8; 24]);
impl XChaChaNonce {
pub fn generate() -> Self {
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
Self(nonce.into())
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 24 {
return Err(CryptoError::InvalidNonceLength {
expected: 24,
actual: bytes.len(),
});
}
let mut nonce = [0u8; 24];
nonce.copy_from_slice(bytes);
Ok(Self(nonce))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
pub struct XChaChaCrypto;
impl XChaChaCrypto {
pub fn encrypt(
key: &XChaChaKey,
nonce: &XChaChaNonce,
plaintext: &[u8],
associated_data: Option<&[u8]>,
) -> Result<Vec<u8>> {
let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
let nonce = InternalXNonce::from_slice(nonce.as_bytes());
let aad = associated_data.unwrap_or(&[]);
cipher
.encrypt(nonce, aead::Payload { msg: plaintext, aad })
.map_err(|e| CryptoError::EncryptionError(e.to_string()))
}
pub fn decrypt(
key: &XChaChaKey,
nonce: &XChaChaNonce,
ciphertext: &[u8],
associated_data: Option<&[u8]>,
) -> Result<Vec<u8>> {
let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
let nonce = InternalXNonce::from_slice(nonce.as_bytes());
let aad = associated_data.unwrap_or(&[]);
cipher
.decrypt(nonce, aead::Payload { msg: ciphertext, aad })
.map_err(|e| CryptoError::DecryptionError(e.to_string()))
}
pub fn encrypt_with_random_nonce(
key: &XChaChaKey,
plaintext: &[u8],
associated_data: Option<&[u8]>,
) -> Result<(XChaChaNonce, Vec<u8>)> {
let nonce = XChaChaNonce::generate();
let ciphertext = Self::encrypt(key, &nonce, plaintext, associated_data)?;
Ok((nonce, ciphertext))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_generation() {
let key = XChaChaKey::generate();
assert_eq!(key.as_bytes().len(), 32);
}
#[test]
fn test_nonce_generation() {
let nonce = XChaChaNonce::generate();
assert_eq!(nonce.as_bytes().len(), 24);
}
#[test]
fn test_encrypt_decrypt() {
let key = XChaChaKey::generate();
let plaintext = b"Hello, World!";
let aad = b"Additional data";
let (nonce, ciphertext) = XChaChaCrypto::encrypt_with_random_nonce(
&key,
plaintext,
Some(aad),
).unwrap();
let decrypted = XChaChaCrypto::decrypt(
&key,
&nonce,
&ciphertext,
Some(aad),
).unwrap();
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_encrypt_decrypt_no_aad() {
let key = XChaChaKey::generate();
let nonce = XChaChaNonce::generate();
let plaintext = b"Secret message";
let ciphertext = XChaChaCrypto::encrypt(&key, &nonce, plaintext, None).unwrap();
let decrypted = XChaChaCrypto::decrypt(&key, &nonce, &ciphertext, None).unwrap();
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_wrong_key_fails() {
let key1 = XChaChaKey::generate();
let key2 = XChaChaKey::generate();
let nonce = XChaChaNonce::generate();
let plaintext = b"Secret";
let ciphertext = XChaChaCrypto::encrypt(&key1, &nonce, plaintext, None).unwrap();
let result = XChaChaCrypto::decrypt(&key2, &nonce, &ciphertext, None);
assert!(result.is_err());
}
}