saorsa_pqc/
symmetric.rs

1//! Quantum-resistant symmetric encryption using ChaCha20-Poly1305
2//!
3//! This module provides symmetric encryption capabilities using ChaCha20-Poly1305,
4//! which is considered quantum-resistant due to its reliance on symmetric cryptography.
5//! ChaCha20-Poly1305 is an AEAD (Authenticated Encryption with Associated Data) cipher
6//! that provides both confidentiality and authenticity.
7//!
8//! # Features
9//! - Quantum-resistant symmetric encryption
10//! - Authenticated encryption with associated data (AEAD)
11//! - 256-bit key support
12//! - Secure random nonce generation
13//! - Memory-safe operations with zeroization
14//! - High performance `ChaCha20` stream cipher with Poly1305 MAC
15//!
16//! # Example
17//! ```ignore
18//! use saorsa_pqc::symmetric::{ChaCha20Poly1305Cipher, SymmetricKey};
19//!
20//! // Generate a new key
21//! let key = SymmetricKey::generate();
22//! let cipher = ChaCha20Poly1305Cipher::new(&key);
23//!
24//! // Encrypt data
25//! let plaintext = b"Hello, quantum-resistant world!";
26//! let (ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
27//!
28//! // Decrypt data
29//! let decrypted = cipher.decrypt(&ciphertext, &nonce, None)?;
30//! assert_eq!(plaintext, &decrypted[..]);
31//! # Ok::<(), Box<dyn std::error::Error>>(())
32//! ```
33
34use chacha20poly1305::{
35    aead::{Aead, AeadCore, KeyInit, OsRng, Payload},
36    ChaCha20Poly1305, Key, Nonce,
37};
38use serde::{Deserialize, Serialize};
39use thiserror::Error;
40use zeroize::{Zeroize, ZeroizeOnDrop};
41
42/// Errors that can occur during symmetric encryption operations
43#[derive(Debug, Error)]
44pub enum SymmetricError {
45    /// Encryption operation failed
46    #[error("Encryption failed")]
47    EncryptionFailed,
48
49    /// Decryption operation failed (may indicate tampering or wrong key)
50    #[error("Decryption failed")]
51    DecryptionFailed,
52
53    /// Key has invalid length (must be 32 bytes for `ChaCha20`)
54    #[error("Invalid key length: expected 32 bytes, got {0}")]
55    InvalidKeyLength(usize),
56
57    /// Nonce has invalid length (must be 12 bytes for ChaCha20-Poly1305)
58    #[error("Invalid nonce length: expected 12 bytes, got {0}")]
59    InvalidNonceLength(usize),
60
61    /// Failed to generate cryptographic key
62    #[error("Key generation failed")]
63    KeyGenerationFailed,
64
65    /// Ciphertext is malformed or corrupted
66    #[error("Invalid ciphertext format")]
67    InvalidCiphertextFormat,
68}
69
70/// A 256-bit symmetric encryption key for ChaCha20-Poly1305
71#[derive(Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
72pub struct SymmetricKey {
73    /// The raw key bytes (32 bytes for `ChaCha20`)
74    key: [u8; 32],
75}
76
77impl SymmetricKey {
78    /// Generate a new random 256-bit symmetric key
79    ///
80    /// Uses the OS random number generator to create a cryptographically secure key.
81    ///
82    /// # Example
83    /// ```rust
84    /// use saorsa_pqc::symmetric::SymmetricKey;
85    ///
86    /// let key = SymmetricKey::generate();
87    /// ```
88    pub fn generate() -> Self {
89        let cipher = ChaCha20Poly1305::generate_key(&mut OsRng);
90        Self {
91            key: *cipher.as_ref(),
92        }
93    }
94
95    /// Create a symmetric key from raw bytes
96    ///
97    /// # Arguments
98    /// * `key_bytes` - A 32-byte array containing the key material
99    ///
100    /// # Example
101    /// ```rust
102    /// use saorsa_pqc::symmetric::SymmetricKey;
103    ///
104    /// let key_bytes = [0u8; 32]; // Not recommended for production!
105    /// let key = SymmetricKey::from_bytes(key_bytes);
106    /// ```
107    #[must_use]
108    pub const fn from_bytes(key_bytes: [u8; 32]) -> Self {
109        Self { key: key_bytes }
110    }
111
112    /// Create a symmetric key from a byte slice
113    ///
114    /// # Arguments
115    /// * `key_bytes` - A byte slice containing exactly 32 bytes
116    ///
117    /// # Errors
118    /// Returns `SymmetricError::InvalidKeyLength` if the slice is not exactly 32 bytes.
119    ///
120    /// # Example
121    /// ```rust
122    /// use saorsa_pqc::symmetric::SymmetricKey;
123    ///
124    /// let key_vec = vec![0u8; 32];
125    /// let key = SymmetricKey::from_slice(&key_vec)?;
126    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
127    /// ```
128    pub fn from_slice(key_bytes: &[u8]) -> Result<Self, SymmetricError> {
129        if key_bytes.len() != 32 {
130            return Err(SymmetricError::InvalidKeyLength(key_bytes.len()));
131        }
132
133        let mut key = [0u8; 32];
134        key.copy_from_slice(key_bytes);
135        Ok(Self { key })
136    }
137
138    /// Get the raw key bytes
139    ///
140    /// # Security Note
141    /// Be careful when using this method as it exposes the raw key material.
142    /// Ensure the returned bytes are properly zeroized after use.
143    #[must_use]
144    pub const fn as_bytes(&self) -> &[u8; 32] {
145        &self.key
146    }
147
148    /// Export the key as a byte vector
149    ///
150    /// This creates a copy of the key bytes. The caller is responsible for
151    /// securely handling and zeroizing the returned vector.
152    #[must_use]
153    pub fn to_bytes(&self) -> Vec<u8> {
154        self.key.to_vec()
155    }
156}
157
158impl std::fmt::Debug for SymmetricKey {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        f.debug_struct("SymmetricKey")
161            .field("key", &"[REDACTED]")
162            .finish()
163    }
164}
165
166/// ChaCha20-Poly1305 AEAD cipher for quantum-resistant symmetric encryption
167///
168/// This struct provides authenticated encryption with associated data using
169/// the `ChaCha20` stream cipher for encryption and Poly1305 for authentication.
170///
171/// ChaCha20-Poly1305 is considered quantum-resistant because:
172/// - It relies on symmetric cryptography rather than mathematical problems
173/// - `ChaCha20` uses a 256-bit key with a large keyspace (2^256)
174/// - Quantum attacks on symmetric ciphers require approximately 2^(n/2) operations
175/// - This means ~2^128 operations for `ChaCha20`, which is still computationally infeasible
176pub struct ChaCha20Poly1305Cipher {
177    /// The ChaCha20-Poly1305 cipher instance
178    cipher: ChaCha20Poly1305,
179}
180
181impl ChaCha20Poly1305Cipher {
182    /// Create a new ChaCha20-Poly1305 cipher with the given key
183    ///
184    /// # Arguments
185    /// * `key` - The symmetric encryption key
186    ///
187    /// # Example
188    /// ```rust
189    /// use saorsa_pqc::symmetric::{ChaCha20Poly1305Cipher, SymmetricKey};
190    ///
191    /// let key = SymmetricKey::generate();
192    /// let cipher = ChaCha20Poly1305Cipher::new(&key);
193    /// ```
194    #[must_use]
195    pub fn new(key: &SymmetricKey) -> Self {
196        let cipher_key = Key::from_slice(key.as_bytes());
197        let cipher = ChaCha20Poly1305::new(cipher_key);
198
199        Self { cipher }
200    }
201
202    /// Encrypt plaintext data
203    ///
204    /// This method encrypts the provided plaintext and returns the ciphertext
205    /// along with the nonce used for encryption. The nonce is randomly generated
206    /// for each encryption operation.
207    ///
208    /// # Arguments
209    /// * `plaintext` - The data to encrypt
210    /// * `associated_data` - Optional associated data that is authenticated but not encrypted
211    ///
212    /// # Returns
213    /// A tuple containing the ciphertext and the nonce used for encryption
214    ///
215    /// # Errors
216    /// Returns `SymmetricError::EncryptionFailed` if encryption fails
217    ///
218    /// # Example
219    /// ```rust
220    /// use saorsa_pqc::symmetric::{ChaCha20Poly1305Cipher, SymmetricKey};
221    ///
222    /// let key = SymmetricKey::generate();
223    /// let cipher = ChaCha20Poly1305Cipher::new(&key);
224    ///
225    /// let plaintext = b"Secret message";
226    /// let (ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
227    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
228    /// ```
229    pub fn encrypt(
230        &self,
231        plaintext: &[u8],
232        associated_data: Option<&[u8]>,
233    ) -> Result<(Vec<u8>, [u8; 12]), SymmetricError> {
234        // Generate a random nonce
235        let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
236
237        // Encrypt the plaintext with optional associated data
238        let payload = match associated_data {
239            Some(aad) => Payload {
240                msg: plaintext,
241                aad,
242            },
243            None => Payload {
244                msg: plaintext,
245                aad: b"",
246            },
247        };
248
249        let ciphertext = self
250            .cipher
251            .encrypt(&nonce, payload)
252            .map_err(|_| SymmetricError::EncryptionFailed)?;
253
254        Ok((ciphertext, *nonce.as_ref()))
255    }
256
257    /// Decrypt ciphertext data
258    ///
259    /// This method decrypts the provided ciphertext using the given nonce.
260    ///
261    /// # Arguments
262    /// * `ciphertext` - The encrypted data to decrypt
263    /// * `nonce` - The nonce that was used during encryption
264    /// * `associated_data` - Optional associated data that was authenticated during encryption
265    ///
266    /// # Returns
267    /// The decrypted plaintext
268    ///
269    /// # Errors
270    /// Returns `SymmetricError::DecryptionFailed` if decryption or authentication fails
271    ///
272    /// # Example
273    /// ```rust
274    /// use saorsa_pqc::symmetric::{ChaCha20Poly1305Cipher, SymmetricKey};
275    ///
276    /// let key = SymmetricKey::generate();
277    /// let cipher = ChaCha20Poly1305Cipher::new(&key);
278    ///
279    /// let plaintext = b"Secret message";
280    /// let (ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
281    /// let decrypted = cipher.decrypt(&ciphertext, &nonce, None)?;
282    /// assert_eq!(plaintext, &decrypted[..]);
283    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
284    /// ```
285    pub fn decrypt(
286        &self,
287        ciphertext: &[u8],
288        nonce: &[u8; 12],
289        associated_data: Option<&[u8]>,
290    ) -> Result<Vec<u8>, SymmetricError> {
291        let nonce = Nonce::from_slice(nonce);
292
293        // Decrypt the ciphertext with optional associated data
294        let payload = match associated_data {
295            Some(aad) => Payload {
296                msg: ciphertext,
297                aad,
298            },
299            None => Payload {
300                msg: ciphertext,
301                aad: b"",
302            },
303        };
304
305        let plaintext = self
306            .cipher
307            .decrypt(nonce, payload)
308            .map_err(|_| SymmetricError::DecryptionFailed)?;
309
310        Ok(plaintext)
311    }
312
313    /// Encrypt with a provided nonce (for testing purposes)
314    ///
315    /// # Security Warning
316    /// Never reuse nonces with the same key! This method is provided primarily
317    /// for testing and should be used with extreme caution in production.
318    ///
319    /// # Arguments
320    /// * `plaintext` - The data to encrypt
321    /// * `nonce` - The nonce to use for encryption
322    /// * `associated_data` - Optional associated data that is authenticated but not encrypted
323    ///
324    /// # Errors
325    /// Returns `SymmetricError::EncryptionFailed` if encryption fails
326    #[cfg(test)]
327    pub fn encrypt_with_nonce(
328        &self,
329        plaintext: &[u8],
330        nonce: &[u8; 12],
331        associated_data: Option<&[u8]>,
332    ) -> Result<Vec<u8>, SymmetricError> {
333        let nonce = Nonce::from_slice(nonce);
334
335        // Encrypt the plaintext with optional associated data
336        let payload = match associated_data {
337            Some(aad) => Payload {
338                msg: plaintext,
339                aad,
340            },
341            None => Payload {
342                msg: plaintext,
343                aad: b"",
344            },
345        };
346
347        let ciphertext = self
348            .cipher
349            .encrypt(&nonce, payload)
350            .map_err(|_| SymmetricError::EncryptionFailed)?;
351
352        Ok(ciphertext)
353    }
354}
355
356impl std::fmt::Debug for ChaCha20Poly1305Cipher {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        f.debug_struct("ChaCha20Poly1305Cipher")
359            .field("cipher", &"[REDACTED]")
360            .finish()
361    }
362}
363
364/// A complete encrypted message containing ciphertext and nonce
365///
366/// This structure packages the encrypted data with its nonce for easy
367/// serialization and transmission.
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct EncryptedMessage {
370    /// The encrypted data
371    pub ciphertext: Vec<u8>,
372    /// The nonce used for encryption
373    pub nonce: [u8; 12],
374    /// Optional associated data that was authenticated
375    pub associated_data: Option<Vec<u8>>,
376}
377
378impl EncryptedMessage {
379    /// Create a new encrypted message
380    ///
381    /// # Arguments
382    /// * `ciphertext` - The encrypted data
383    /// * `nonce` - The nonce used for encryption
384    /// * `associated_data` - Optional associated data
385    #[must_use]
386    pub const fn new(
387        ciphertext: Vec<u8>,
388        nonce: [u8; 12],
389        associated_data: Option<Vec<u8>>,
390    ) -> Self {
391        Self {
392            ciphertext,
393            nonce,
394            associated_data,
395        }
396    }
397
398    /// Decrypt this message using the provided cipher
399    ///
400    /// # Arguments
401    /// * `cipher` - The cipher to use for decryption
402    ///
403    /// # Returns
404    /// The decrypted plaintext
405    ///
406    /// # Errors
407    /// Returns `SymmetricError::DecryptionFailed` if decryption fails
408    pub fn decrypt(&self, cipher: &ChaCha20Poly1305Cipher) -> Result<Vec<u8>, SymmetricError> {
409        cipher.decrypt(
410            &self.ciphertext,
411            &self.nonce,
412            self.associated_data.as_deref(),
413        )
414    }
415
416    /// Get the size of the encrypted message in bytes
417    #[must_use]
418    pub fn size(&self) -> usize {
419        self.ciphertext.len() + 12 + self.associated_data.as_ref().map_or(0, std::vec::Vec::len)
420    }
421}
422
423/// Utility functions for symmetric encryption
424pub mod utils {
425    use super::{ChaCha20Poly1305Cipher, EncryptedMessage, SymmetricError, SymmetricKey};
426
427    /// Encrypt data and return a complete `EncryptedMessage`
428    ///
429    /// # Arguments
430    /// * `key` - The symmetric encryption key
431    /// * `plaintext` - The data to encrypt
432    /// * `associated_data` - Optional associated data
433    ///
434    /// # Returns
435    /// An `EncryptedMessage` containing the ciphertext, nonce, and associated data
436    ///
437    /// # Example
438    /// ```rust
439    /// use saorsa_pqc::symmetric::{SymmetricKey, utils};
440    ///
441    /// let key = SymmetricKey::generate();
442    /// let message = utils::encrypt_message(&key, b"Hello, world!", None)?;
443    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
444    /// ```
445    pub fn encrypt_message(
446        key: &SymmetricKey,
447        plaintext: &[u8],
448        associated_data: Option<&[u8]>,
449    ) -> Result<EncryptedMessage, SymmetricError> {
450        let cipher = ChaCha20Poly1305Cipher::new(key);
451        let (ciphertext, nonce) = cipher.encrypt(plaintext, associated_data)?;
452
453        Ok(EncryptedMessage::new(
454            ciphertext,
455            nonce,
456            associated_data.map(<[u8]>::to_vec),
457        ))
458    }
459
460    /// Decrypt an `EncryptedMessage`
461    ///
462    /// # Arguments
463    /// * `key` - The symmetric encryption key
464    /// * `message` - The encrypted message to decrypt
465    ///
466    /// # Returns
467    /// The decrypted plaintext
468    ///
469    /// # Example
470    /// ```rust
471    /// use saorsa_pqc::symmetric::{SymmetricKey, utils};
472    ///
473    /// let key = SymmetricKey::generate();
474    /// let message = utils::encrypt_message(&key, b"Hello, world!", None)?;
475    /// let plaintext = utils::decrypt_message(&key, &message)?;
476    /// assert_eq!(b"Hello, world!", &plaintext[..]);
477    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
478    /// ```
479    pub fn decrypt_message(
480        key: &SymmetricKey,
481        message: &EncryptedMessage,
482    ) -> Result<Vec<u8>, SymmetricError> {
483        let cipher = ChaCha20Poly1305Cipher::new(key);
484        message.decrypt(&cipher)
485    }
486
487    /// Generate a key from a password using PBKDF2
488    ///
489    /// # Arguments
490    /// * `password` - The password to derive a key from
491    /// * `salt` - A random salt (should be at least 16 bytes)
492    /// * `iterations` - Number of PBKDF2 iterations (recommended: 100,000+)
493    ///
494    /// # Returns
495    /// A derived symmetric key
496    ///
497    /// # Security Note
498    /// This function uses SHA-256 as the hash function for PBKDF2.
499    /// The salt should be randomly generated and stored alongside the encrypted data.
500    /// The iteration count should be chosen based on your security requirements and
501    /// performance constraints.
502    ///
503    /// # Example
504    /// ```rust
505    /// use saorsa_pqc::symmetric::utils;
506    ///
507    /// let password = b"my_secure_password";
508    /// let salt = b"random_salt_16bytes"; // In practice, use random bytes
509    /// let key = utils::derive_key_from_password(password, salt, 100_000)?;
510    /// # Ok::<(), saorsa_pqc::symmetric::SymmetricError>(())
511    /// ```
512    pub fn derive_key_from_password(
513        password: &[u8],
514        salt: &[u8],
515        iterations: u32,
516    ) -> Result<SymmetricKey, SymmetricError> {
517        use pbkdf2::pbkdf2_hmac_array;
518        use sha2::Sha256;
519
520        // Use PBKDF2 to derive a key
521        let key = pbkdf2_hmac_array::<Sha256, 32>(password, salt, iterations);
522
523        Ok(SymmetricKey::from_bytes(key))
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530
531    #[test]
532    fn test_key_generation() {
533        let key1 = SymmetricKey::generate();
534        let key2 = SymmetricKey::generate();
535
536        // Keys should be different
537        assert_ne!(key1.as_bytes(), key2.as_bytes());
538        assert_eq!(key1.as_bytes().len(), 32);
539    }
540
541    #[test]
542    fn test_key_from_bytes() {
543        let key_bytes = [42u8; 32];
544        let key = SymmetricKey::from_bytes(key_bytes);
545        assert_eq!(key.as_bytes(), &key_bytes);
546    }
547
548    #[test]
549    fn test_key_from_slice() {
550        let key_vec = vec![42u8; 32];
551        let key = SymmetricKey::from_slice(&key_vec).unwrap();
552        assert_eq!(key.as_bytes(), &[42u8; 32]);
553
554        // Test invalid length
555        let invalid_key = vec![42u8; 31];
556        assert!(SymmetricKey::from_slice(&invalid_key).is_err());
557    }
558
559    #[test]
560    fn test_basic_encryption_decryption() -> Result<(), SymmetricError> {
561        let key = SymmetricKey::generate();
562        let cipher = ChaCha20Poly1305Cipher::new(&key);
563
564        let plaintext = b"Hello, quantum-resistant world!";
565        let (ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
566
567        // Ciphertext should be different from plaintext
568        assert_ne!(ciphertext, plaintext);
569        assert_eq!(nonce.len(), 12);
570
571        // Decrypt and verify
572        let decrypted = cipher.decrypt(&ciphertext, &nonce, None)?;
573        assert_eq!(plaintext, &decrypted[..]);
574
575        Ok(())
576    }
577
578    #[test]
579    fn test_encryption_with_associated_data() -> Result<(), SymmetricError> {
580        let key = SymmetricKey::generate();
581        let cipher = ChaCha20Poly1305Cipher::new(&key);
582
583        let plaintext = b"Secret message";
584        let associated_data = b"public metadata";
585        let (ciphertext, nonce) = cipher.encrypt(plaintext, Some(associated_data))?;
586
587        // Decrypt with correct associated data
588        let decrypted = cipher.decrypt(&ciphertext, &nonce, Some(associated_data))?;
589        assert_eq!(plaintext, &decrypted[..]);
590
591        // Decrypt with wrong associated data should fail
592        let wrong_associated_data = b"wrong metadata";
593        assert!(cipher
594            .decrypt(&ciphertext, &nonce, Some(wrong_associated_data))
595            .is_err());
596
597        Ok(())
598    }
599
600    #[test]
601    fn test_different_nonces_produce_different_ciphertext() -> Result<(), SymmetricError> {
602        let key = SymmetricKey::generate();
603        let cipher = ChaCha20Poly1305Cipher::new(&key);
604
605        let plaintext = b"Same message";
606        let (ciphertext1, nonce1) = cipher.encrypt(plaintext, None)?;
607        let (ciphertext2, nonce2) = cipher.encrypt(plaintext, None)?;
608
609        // Different encryptions should produce different ciphertext and nonces
610        assert_ne!(ciphertext1, ciphertext2);
611        assert_ne!(nonce1, nonce2);
612
613        // Both should decrypt to the same plaintext
614        let decrypted1 = cipher.decrypt(&ciphertext1, &nonce1, None)?;
615        let decrypted2 = cipher.decrypt(&ciphertext2, &nonce2, None)?;
616        assert_eq!(decrypted1, decrypted2);
617        assert_eq!(plaintext, &decrypted1[..]);
618
619        Ok(())
620    }
621
622    #[test]
623    fn test_tampering_detection() -> Result<(), SymmetricError> {
624        let key = SymmetricKey::generate();
625        let cipher = ChaCha20Poly1305Cipher::new(&key);
626
627        let plaintext = b"Important message";
628        let (mut ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
629
630        // Tamper with the ciphertext
631        if let Some(byte) = ciphertext.get_mut(0) {
632            *byte = byte.wrapping_add(1);
633        }
634
635        // Decryption should fail due to authentication failure
636        assert!(cipher.decrypt(&ciphertext, &nonce, None).is_err());
637
638        Ok(())
639    }
640
641    #[test]
642    fn test_encrypted_message() -> Result<(), SymmetricError> {
643        let key = SymmetricKey::generate();
644        let plaintext = b"Test message";
645        let associated_data = Some(b"metadata".as_slice());
646
647        let message = utils::encrypt_message(&key, plaintext, associated_data)?;
648        assert!(message.size() > 0);
649
650        let decrypted = utils::decrypt_message(&key, &message)?;
651        assert_eq!(plaintext, &decrypted[..]);
652
653        Ok(())
654    }
655
656    #[test]
657    fn test_key_derivation_from_password() -> Result<(), SymmetricError> {
658        let password = b"my_secure_password";
659        let salt = b"random_salt_1234";
660        let iterations = 1000; // Low for testing
661
662        let key1 = utils::derive_key_from_password(password, salt, iterations)?;
663        let key2 = utils::derive_key_from_password(password, salt, iterations)?;
664
665        // Same inputs should produce same key
666        assert_eq!(key1.as_bytes(), key2.as_bytes());
667
668        // Different salt should produce different key
669        let different_salt = b"different_salt12";
670        let key3 = utils::derive_key_from_password(password, different_salt, iterations)?;
671        assert_ne!(key1.as_bytes(), key3.as_bytes());
672
673        Ok(())
674    }
675
676    #[test]
677    fn test_empty_plaintext() -> Result<(), SymmetricError> {
678        let key = SymmetricKey::generate();
679        let cipher = ChaCha20Poly1305Cipher::new(&key);
680
681        let plaintext = b"";
682        let (ciphertext, nonce) = cipher.encrypt(plaintext, None)?;
683
684        let decrypted = cipher.decrypt(&ciphertext, &nonce, None)?;
685        assert_eq!(plaintext, &decrypted[..]);
686
687        Ok(())
688    }
689
690    #[test]
691    fn test_large_plaintext() -> Result<(), SymmetricError> {
692        let key = SymmetricKey::generate();
693        let cipher = ChaCha20Poly1305Cipher::new(&key);
694
695        // Test with 1MB of data
696        let plaintext = vec![42u8; 1024 * 1024];
697        let (ciphertext, nonce) = cipher.encrypt(&plaintext, None)?;
698
699        let decrypted = cipher.decrypt(&ciphertext, &nonce, None)?;
700        assert_eq!(plaintext, decrypted);
701
702        Ok(())
703    }
704
705    #[test]
706    fn test_key_zeroization() {
707        let mut key = SymmetricKey::generate();
708        let original_bytes = *key.as_bytes();
709
710        // Explicitly zeroize the key
711        key.zeroize();
712
713        // The key should now be all zeros
714        assert_eq!(key.as_bytes(), &[0u8; 32]);
715        assert_ne!(&original_bytes, &[0u8; 32]); // Original wasn't all zeros
716    }
717
718    #[test]
719    fn test_deterministic_encryption_for_testing() -> Result<(), SymmetricError> {
720        let key = SymmetricKey::from_bytes([1u8; 32]);
721        let cipher = ChaCha20Poly1305Cipher::new(&key);
722
723        let plaintext = b"Test message";
724        let nonce = [2u8; 12];
725
726        let ciphertext1 = cipher.encrypt_with_nonce(plaintext, &nonce, None)?;
727        let ciphertext2 = cipher.encrypt_with_nonce(plaintext, &nonce, None)?;
728
729        // With same key, plaintext, and nonce, ciphertext should be identical
730        assert_eq!(ciphertext1, ciphertext2);
731
732        // Should decrypt correctly
733        let decrypted = cipher.decrypt(&ciphertext1, &nonce, None)?;
734        assert_eq!(plaintext, &decrypted[..]);
735
736        Ok(())
737    }
738}