Jazz Turner-Baggs 9a94f9a6d6
Flatten Repos (#70)
* move to “crates” style folder

* Update workspace

* clear crate names

* Rename crate folders based on feedback

* Use workspace dependencies instead of paths

* Move updated files from core
2026-03-24 18:21:00 -07:00

163 lines
4.5 KiB
Rust

use chacha20poly1305::{
ChaCha20Poly1305, Key, Nonce as ChaChaNonce,
aead::{Aead, KeyInit},
};
use crate::types::{MessageKey, Nonce};
/// Encrypts plaintext with the given key and AAD.
///
/// # Arguments
///
/// * `message_key` - The message key.
/// * `plaintext` - The plaintext to encrypt.
/// * `aad` - The additional authenticated data.
///
/// # Returns
///
/// A tuple containing the ciphertext and the randomly generated nonce.
pub fn encrypt(message_key: &MessageKey, plaintext: &[u8], aad: &[u8]) -> (Vec<u8>, Nonce) {
let cipher = ChaCha20Poly1305::new(Key::from_slice(message_key));
let nonce = rand::random::<Nonce>();
let ciphertext = cipher
.encrypt(
ChaChaNonce::from_slice(&nonce),
chacha20poly1305::aead::Payload {
msg: plaintext,
aad,
},
)
.expect("encryption failure");
(ciphertext, nonce)
}
/// Decrypts ciphertext with the given key, nonce, and AAD.
///
/// # Arguments
///
/// * `message_key` - The message key.
/// * `ciphertext` - The ciphertext to decrypt.
/// * `nonce` - The nonce used for encryption.
/// * `aad` - The additional authenticated data.
///
/// # Returns
///
/// Ok(plaintext) on success, Err on authentication or decryption failure.
pub fn decrypt(
message_key: &MessageKey,
ciphertext: &[u8],
nonce: &Nonce,
aad: &[u8],
) -> Result<Vec<u8>, String> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(message_key));
cipher
.decrypt(
ChaChaNonce::from_slice(nonce),
chacha20poly1305::aead::Payload {
msg: ciphertext,
aad,
},
)
.map_err(|_| "Decryption failed: invalid ciphertext, nonce, key, or AAD".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt_roundtrip_no_aad() {
let message_key = rand::random::<[u8; 32]>();
let plaintext = b"Hello, this is a test message!";
let aad = b""; // Empty AAD
let (ciphertext, nonce) = encrypt(&message_key, plaintext, aad);
let decrypted = decrypt(&message_key, &ciphertext, &nonce, aad);
assert!(decrypted.is_ok());
assert_eq!(decrypted.unwrap(), plaintext);
}
#[test]
fn test_encrypt_decrypt_roundtrip_with_aad() {
let message_key = rand::random::<[u8; 32]>();
let plaintext = b"Secret payload";
let aad = b"public header data";
let (ciphertext, nonce) = encrypt(&message_key, plaintext, aad);
let decrypted = decrypt(&message_key, &ciphertext, &nonce, aad);
assert!(decrypted.is_ok());
assert_eq!(decrypted.unwrap(), plaintext);
}
#[test]
fn test_decrypt_tampered_ciphertext_fails() {
let message_key = rand::random::<[u8; 32]>();
let plaintext = b"Important data";
let aad = b"metadata";
let (mut ciphertext, nonce) = encrypt(&message_key, plaintext, aad);
// Tamper with the ciphertext
if !ciphertext.is_empty() {
ciphertext[0] ^= 0xFF;
}
let result = decrypt(&message_key, &ciphertext, &nonce, aad);
assert!(result.is_err());
}
#[test]
fn test_decrypt_wrong_aad_fails() {
let message_key = rand::random::<[u8; 32]>();
let plaintext = b"Data";
let correct_aad = b"correct AAD";
let wrong_aad = b"wrong AAD";
let (ciphertext, nonce) = encrypt(&message_key, plaintext, correct_aad);
let result = decrypt(&message_key, &ciphertext, &nonce, wrong_aad);
assert!(result.is_err());
}
#[test]
fn test_decrypt_wrong_key_fails() {
let correct_key = rand::random::<[u8; 32]>();
let mut wrong_key = correct_key;
wrong_key[0] ^= 0xFF; // Flip one bit
let plaintext = b"Test";
let aad = b"";
let (ciphertext, nonce) = encrypt(&correct_key, plaintext, aad);
let result = decrypt(&wrong_key, &ciphertext, &nonce, aad);
assert!(result.is_err());
}
#[test]
fn test_empty_plaintext() {
let message_key = [0u8; 32];
let plaintext = b"";
let aad = b"some aad";
let (ciphertext, nonce) = encrypt(&message_key, plaintext, aad);
// Ciphertext should be exactly 16 bytes (the Poly1305 tag) for empty message
assert_eq!(ciphertext.len(), 16);
let decrypted = decrypt(&message_key, &ciphertext, &nonce, aad);
assert!(decrypted.is_ok());
assert_eq!(decrypted.unwrap(), plaintext);
}
}