pq_msg/exchange/encryptor.rs
1use chacha20poly1305::{KeyInit, XChaCha20Poly1305, aead::Aead};
2
3use crate::errors::CryptoError;
4
5/// An encryptor utilizing XChaCha20Poly1305 authenticated encryption
6///
7/// This struct provides an interface for encrypting and decrypting data using
8/// a 32-byte symmetric key. It uses XChaCha20Poly1305 for authenticated
9/// encryption with associated data (AEAD). The key is wiped from memory on drop.
10///
11/// Derive the key with a KDF (as `MessageSession` does) rather than using a raw
12/// KEM shared secret, and never let two parties encrypt under the same key.
13pub struct Encryptor {
14 cipher: XChaCha20Poly1305,
15}
16
17impl Encryptor {
18 /// Creates a new encryptor with the given key
19 ///
20 /// # Arguments
21 /// * `key` - The 32-byte key to use for encryption/decryption
22 ///
23 /// # Returns
24 /// A new Encryptor instance initialized with the provided key
25 pub fn new(key: &[u8; 32]) -> Self {
26 Self {
27 cipher: XChaCha20Poly1305::new(key.into()),
28 }
29 }
30
31 /// Encrypts plaintext using XChaCha20Poly1305 with the stored key
32 ///
33 /// # Arguments
34 /// * `plaintext` - The data to encrypt
35 /// * `nonce` - A 24-byte nonce (must be unique for each encryption with the same key)
36 ///
37 /// # Returns
38 /// - `Result<Vec<u8>, CryptoError>`: The encrypted ciphertext or an error
39 ///
40 /// # Security Notes
41 /// - The nonce must never be reused with the same key
42 /// - The ciphertext includes an authentication tag to verify integrity
43 pub fn encrypt(&self, plaintext: &[u8], nonce: &[u8; 24]) -> Result<Vec<u8>, CryptoError> {
44 Ok(self.cipher.encrypt(nonce.into(), plaintext)?)
45 }
46
47 /// Decrypts ciphertext using XChaCha20Poly1305 with the stored key
48 ///
49 /// # Arguments
50 /// * `ciphertext` - The encrypted data to decrypt
51 /// * `nonce` - The 24-byte nonce used during encryption
52 ///
53 /// # Returns
54 /// - `Result<Vec<u8>, CryptoError>`: The decrypted plaintext or an error
55 ///
56 /// # Security Notes
57 /// - This function will return an error if the ciphertext has been tampered with
58 /// - The same nonce used for encryption must be provided for decryption
59 pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; 24]) -> Result<Vec<u8>, CryptoError> {
60 Ok(self.cipher.decrypt(nonce.into(), ciphertext)?)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_encryption_decryption() {
70 let encryptor = Encryptor::new(&[7u8; 32]);
71
72 let plaintext = b"Hello, world!";
73 let nonce = b"the length of this is 24";
74
75 let mut ciphertext = encryptor.encrypt(plaintext, nonce).unwrap();
76 let decrypted_plaintext = encryptor.decrypt(&ciphertext, nonce).unwrap();
77
78 assert_eq!(plaintext.to_vec(), decrypted_plaintext);
79
80 // Tampering is detected
81 ciphertext[0] ^= 1;
82 assert!(encryptor.decrypt(&ciphertext, nonce).is_err());
83 }
84}