Skip to main content

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}