pq_msg/exchange/
encryptor.rs

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