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}