use aes_gcm::{aead::Aead as AesAead, Aes256Gcm};
use chacha20poly1305::{
aead::{KeyInit, Payload},
ChaCha20Poly1305, Key,
};
use thiserror::Error;
use crate::security::crypto::random;
#[derive(Debug, Error)]
pub enum SymmetricError {
#[error("Encryption error: {0}")]
EncryptionError(String),
#[error("Decryption error: {0}")]
DecryptionError(String),
#[error("Invalid key error: {0}")]
InvalidKeyError(String),
#[error("Invalid data error: {0}")]
InvalidDataError(String),
#[error("Invalid nonce error: {0}")]
InvalidNonceError(String),
#[error("Other error: {0}")]
OtherError(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymmetricAlgorithm {
Aes256Gcm,
Aes256Cbc,
Aes256Ctr,
ChaCha20Poly1305,
}
#[derive(Debug)]
pub struct SymmetricCrypto {
algorithm: SymmetricAlgorithm,
}
impl SymmetricCrypto {
pub fn new(algorithm: SymmetricAlgorithm) -> Self {
Self { algorithm }
}
pub fn generate_key(&self) -> Vec<u8> {
match self.algorithm {
SymmetricAlgorithm::Aes256Gcm
| SymmetricAlgorithm::Aes256Cbc
| SymmetricAlgorithm::Aes256Ctr => {
random::random_bytes(32)
}
SymmetricAlgorithm::ChaCha20Poly1305 => {
random::random_bytes(32)
}
}
}
pub fn generate_nonce(&self) -> Vec<u8> {
match self.algorithm {
SymmetricAlgorithm::Aes256Gcm => {
random::random_bytes(12)
}
SymmetricAlgorithm::Aes256Cbc => {
random::random_bytes(16)
}
SymmetricAlgorithm::Aes256Ctr => {
random::random_bytes(16)
}
SymmetricAlgorithm::ChaCha20Poly1305 => {
random::random_bytes(12)
}
}
}
pub fn encrypt(
&self,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
match self.algorithm {
SymmetricAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(key, nonce, plaintext, aad),
SymmetricAlgorithm::ChaCha20Poly1305 => {
self.encrypt_chacha20_poly1305(key, nonce, plaintext, aad)
}
_ => Err(SymmetricError::EncryptionError(format!(
"Algorithm {:?} not yet implemented",
self.algorithm
))),
}
}
pub fn decrypt(
&self,
key: &[u8],
nonce: &[u8],
ciphertext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
match self.algorithm {
SymmetricAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(key, nonce, ciphertext, aad),
SymmetricAlgorithm::ChaCha20Poly1305 => {
self.decrypt_chacha20_poly1305(key, nonce, ciphertext, aad)
}
_ => Err(SymmetricError::DecryptionError(format!(
"Algorithm {:?} not yet implemented",
self.algorithm
))),
}
}
fn encrypt_aes_gcm(
&self,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
if key.len() != 32 {
return Err(SymmetricError::InvalidKeyError(format!(
"AES-256-GCM requires a 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 12 {
return Err(SymmetricError::InvalidNonceError(format!(
"AES-256-GCM requires a 12-byte nonce, got {}",
nonce.len()
)));
}
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|e| SymmetricError::EncryptionError(e.to_string()))?;
let nonce = aes_gcm::Nonce::from_slice(nonce);
let payload = if let Some(aad_data) = aad {
aes_gcm::aead::Payload {
msg: plaintext,
aad: aad_data,
}
} else {
aes_gcm::aead::Payload {
msg: plaintext,
aad: &[],
}
};
cipher
.encrypt(nonce, payload)
.map_err(|e| SymmetricError::EncryptionError(e.to_string()))
}
fn decrypt_aes_gcm(
&self,
key: &[u8],
nonce: &[u8],
ciphertext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
if key.len() != 32 {
return Err(SymmetricError::InvalidKeyError(format!(
"AES-256-GCM requires a 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 12 {
return Err(SymmetricError::InvalidNonceError(format!(
"AES-256-GCM requires a 12-byte nonce, got {}",
nonce.len()
)));
}
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|e| SymmetricError::DecryptionError(e.to_string()))?;
let nonce = aes_gcm::Nonce::from_slice(nonce);
let payload = if let Some(aad_data) = aad {
aes_gcm::aead::Payload {
msg: ciphertext,
aad: aad_data,
}
} else {
aes_gcm::aead::Payload {
msg: ciphertext,
aad: &[],
}
};
cipher
.decrypt(nonce, payload)
.map_err(|e| SymmetricError::DecryptionError(e.to_string()))
}
fn encrypt_chacha20_poly1305(
&self,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
if key.len() != 32 {
return Err(SymmetricError::InvalidKeyError(format!(
"ChaCha20-Poly1305 requires a 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 12 {
return Err(SymmetricError::InvalidNonceError(format!(
"ChaCha20-Poly1305 requires a 12-byte nonce, got {}",
nonce.len()
)));
}
let key = Key::from_slice(key);
let cipher = ChaCha20Poly1305::new(key);
let nonce = chacha20poly1305::Nonce::from_slice(nonce);
let payload = if let Some(aad_data) = aad {
Payload {
msg: plaintext,
aad: aad_data,
}
} else {
Payload {
msg: plaintext,
aad: &[],
}
};
cipher
.encrypt(nonce, payload)
.map_err(|e| SymmetricError::EncryptionError(e.to_string()))
}
fn decrypt_chacha20_poly1305(
&self,
key: &[u8],
nonce: &[u8],
ciphertext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, SymmetricError> {
if key.len() != 32 {
return Err(SymmetricError::InvalidKeyError(format!(
"ChaCha20-Poly1305 requires a 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 12 {
return Err(SymmetricError::InvalidNonceError(format!(
"ChaCha20-Poly1305 requires a 12-byte nonce, got {}",
nonce.len()
)));
}
let key = Key::from_slice(key);
let cipher = ChaCha20Poly1305::new(key);
let nonce = chacha20poly1305::Nonce::from_slice(nonce);
let payload = if let Some(aad_data) = aad {
Payload {
msg: ciphertext,
aad: aad_data,
}
} else {
Payload {
msg: ciphertext,
aad: &[],
}
};
cipher
.decrypt(nonce, payload)
.map_err(|e| SymmetricError::DecryptionError(e.to_string()))
}
}
pub fn create_aes_256_gcm() -> SymmetricCrypto {
SymmetricCrypto::new(SymmetricAlgorithm::Aes256Gcm)
}
pub fn create_chacha20_poly1305() -> SymmetricCrypto {
SymmetricCrypto::new(SymmetricAlgorithm::ChaCha20Poly1305)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aes_gcm() -> Result<(), Box<dyn std::error::Error>> {
let crypto = SymmetricCrypto::new(SymmetricAlgorithm::Aes256Gcm);
let key = crypto.generate_key();
let nonce = crypto.generate_nonce();
let plaintext = b"This is a test message";
let aad = b"Additional authenticated data";
let ciphertext = crypto.encrypt(&key, &nonce, plaintext, Some(aad))?;
assert_ne!(&ciphertext, plaintext);
let decrypted = crypto.decrypt(&key, &nonce, &ciphertext, Some(aad))?;
assert_eq!(&decrypted, plaintext);
let wrong_aad = b"Wrong additional data";
let result = crypto.decrypt(&key, &nonce, &ciphertext, Some(wrong_aad));
assert!(result.is_err());
Ok(())
}
#[test]
fn test_chacha20_poly1305() -> Result<(), Box<dyn std::error::Error>> {
let crypto = SymmetricCrypto::new(SymmetricAlgorithm::ChaCha20Poly1305);
let key = crypto.generate_key();
let nonce = crypto.generate_nonce();
let plaintext = b"This is a test message for ChaCha20-Poly1305";
let ciphertext = crypto.encrypt(&key, &nonce, plaintext, None)?;
assert_ne!(&ciphertext, plaintext);
let decrypted = crypto.decrypt(&key, &nonce, &ciphertext, None)?;
assert_eq!(&decrypted, plaintext);
Ok(())
}
#[test]
fn test_helper_functions() {
let aes_crypto = create_aes_256_gcm();
let chacha_crypto = create_chacha20_poly1305();
assert_eq!(aes_crypto.algorithm, SymmetricAlgorithm::Aes256Gcm);
assert_eq!(
chacha_crypto.algorithm,
SymmetricAlgorithm::ChaCha20Poly1305
);
}
}