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}