chie_crypto/
encryption.rs

1//! Content encryption using ChaCha20-Poly1305.
2
3use chacha20poly1305::{
4    ChaCha20Poly1305, Nonce,
5    aead::{Aead, KeyInit},
6};
7use rand::RngCore;
8use thiserror::Error;
9
10/// Encryption key (256 bits).
11pub type EncryptionKey = [u8; 32];
12
13/// Nonce for encryption (96 bits).
14pub type EncryptionNonce = [u8; 12];
15
16#[derive(Debug, Error)]
17pub enum EncryptionError {
18    #[error("Encryption failed")]
19    EncryptionFailed,
20
21    #[error("Decryption failed")]
22    DecryptionFailed,
23}
24
25/// Generate a random encryption key.
26pub fn generate_key() -> EncryptionKey {
27    let mut key = [0u8; 32];
28    rand::thread_rng().fill_bytes(&mut key);
29    key
30}
31
32/// Generate a random nonce.
33pub fn generate_nonce() -> EncryptionNonce {
34    let mut nonce = [0u8; 12];
35    rand::thread_rng().fill_bytes(&mut nonce);
36    nonce
37}
38
39/// Encrypt data using ChaCha20-Poly1305.
40pub fn encrypt(
41    data: &[u8],
42    key: &EncryptionKey,
43    nonce: &EncryptionNonce,
44) -> Result<Vec<u8>, EncryptionError> {
45    let cipher = ChaCha20Poly1305::new(key.into());
46    let nonce = Nonce::from_slice(nonce);
47
48    cipher
49        .encrypt(nonce, data)
50        .map_err(|_| EncryptionError::EncryptionFailed)
51}
52
53/// Decrypt data using ChaCha20-Poly1305.
54pub fn decrypt(
55    ciphertext: &[u8],
56    key: &EncryptionKey,
57    nonce: &EncryptionNonce,
58) -> Result<Vec<u8>, EncryptionError> {
59    let cipher = ChaCha20Poly1305::new(key.into());
60    let nonce = Nonce::from_slice(nonce);
61
62    cipher
63        .decrypt(nonce, ciphertext)
64        .map_err(|_| EncryptionError::DecryptionFailed)
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_encrypt_decrypt() {
73        let key = generate_key();
74        let nonce = generate_nonce();
75        let plaintext = b"Hello, CHIE Protocol!";
76
77        let ciphertext = encrypt(plaintext, &key, &nonce).unwrap();
78        let decrypted = decrypt(&ciphertext, &key, &nonce).unwrap();
79
80        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
81    }
82
83    #[test]
84    fn test_decrypt_with_wrong_key() {
85        let key1 = generate_key();
86        let key2 = generate_key();
87        let nonce = generate_nonce();
88        let plaintext = b"Secret message";
89
90        let ciphertext = encrypt(plaintext, &key1, &nonce).unwrap();
91        let result = decrypt(&ciphertext, &key2, &nonce);
92
93        assert!(result.is_err());
94        assert!(matches!(result, Err(EncryptionError::DecryptionFailed)));
95    }
96
97    #[test]
98    fn test_decrypt_with_wrong_nonce() {
99        let key = generate_key();
100        let nonce1 = generate_nonce();
101        let nonce2 = generate_nonce();
102        let plaintext = b"Secret message";
103
104        let ciphertext = encrypt(plaintext, &key, &nonce1).unwrap();
105        let result = decrypt(&ciphertext, &key, &nonce2);
106
107        assert!(result.is_err());
108        assert!(matches!(result, Err(EncryptionError::DecryptionFailed)));
109    }
110
111    #[test]
112    fn test_nonce_reuse_different_plaintexts() {
113        let key = generate_key();
114        let nonce = generate_nonce();
115        let plaintext1 = b"First message";
116        let plaintext2 = b"Second message";
117
118        // Same nonce, different plaintexts produce different ciphertexts
119        let ciphertext1 = encrypt(plaintext1, &key, &nonce).unwrap();
120        let ciphertext2 = encrypt(plaintext2, &key, &nonce).unwrap();
121
122        assert_ne!(ciphertext1, ciphertext2);
123
124        // Both can be decrypted correctly (though nonce reuse is bad practice)
125        let decrypted1 = decrypt(&ciphertext1, &key, &nonce).unwrap();
126        let decrypted2 = decrypt(&ciphertext2, &key, &nonce).unwrap();
127
128        assert_eq!(plaintext1.as_slice(), decrypted1.as_slice());
129        assert_eq!(plaintext2.as_slice(), decrypted2.as_slice());
130    }
131
132    #[test]
133    fn test_empty_data_encryption() {
134        let key = generate_key();
135        let nonce = generate_nonce();
136        let plaintext = b"";
137
138        let ciphertext = encrypt(plaintext, &key, &nonce).unwrap();
139        let decrypted = decrypt(&ciphertext, &key, &nonce).unwrap();
140
141        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
142        // ChaCha20-Poly1305 adds authentication tag even for empty data
143        assert!(!ciphertext.is_empty());
144    }
145
146    #[test]
147    fn test_large_data_encryption() {
148        let key = generate_key();
149        let nonce = generate_nonce();
150        // Test with 1MB of data
151        let plaintext = vec![0x42u8; 1024 * 1024];
152
153        let ciphertext = encrypt(&plaintext, &key, &nonce).unwrap();
154        let decrypted = decrypt(&ciphertext, &key, &nonce).unwrap();
155
156        assert_eq!(plaintext, decrypted);
157        // Ciphertext should be plaintext + 16 bytes (Poly1305 tag)
158        assert_eq!(ciphertext.len(), plaintext.len() + 16);
159    }
160
161    #[test]
162    fn test_corrupted_ciphertext() {
163        let key = generate_key();
164        let nonce = generate_nonce();
165        let plaintext = b"Important message";
166
167        let mut ciphertext = encrypt(plaintext, &key, &nonce).unwrap();
168        // Corrupt one byte
169        ciphertext[0] ^= 0xFF;
170
171        let result = decrypt(&ciphertext, &key, &nonce);
172        assert!(result.is_err());
173        assert!(matches!(result, Err(EncryptionError::DecryptionFailed)));
174    }
175
176    #[test]
177    fn test_key_generation_randomness() {
178        let key1 = generate_key();
179        let key2 = generate_key();
180        let key3 = generate_key();
181
182        // Keys should be different
183        assert_ne!(key1, key2);
184        assert_ne!(key2, key3);
185        assert_ne!(key1, key3);
186
187        // Keys should not be all zeros
188        assert_ne!(key1, [0u8; 32]);
189        assert_ne!(key2, [0u8; 32]);
190    }
191
192    #[test]
193    fn test_nonce_generation_randomness() {
194        let nonce1 = generate_nonce();
195        let nonce2 = generate_nonce();
196        let nonce3 = generate_nonce();
197
198        // Nonces should be different
199        assert_ne!(nonce1, nonce2);
200        assert_ne!(nonce2, nonce3);
201        assert_ne!(nonce1, nonce3);
202
203        // Nonces should not be all zeros
204        assert_ne!(nonce1, [0u8; 12]);
205        assert_ne!(nonce2, [0u8; 12]);
206    }
207
208    #[test]
209    fn test_deterministic_encryption_same_inputs() {
210        let key = generate_key();
211        let nonce = [0u8; 12]; // Fixed nonce for determinism test
212        let plaintext = b"Deterministic test";
213
214        let ciphertext1 = encrypt(plaintext, &key, &nonce).unwrap();
215        let ciphertext2 = encrypt(plaintext, &key, &nonce).unwrap();
216
217        // Same key, nonce, and plaintext should produce same ciphertext
218        assert_eq!(ciphertext1, ciphertext2);
219    }
220
221    #[test]
222    fn test_truncated_ciphertext() {
223        let key = generate_key();
224        let nonce = generate_nonce();
225        let plaintext = b"Test message for truncation";
226
227        let mut ciphertext = encrypt(plaintext, &key, &nonce).unwrap();
228        // Truncate the ciphertext (remove authentication tag)
229        ciphertext.truncate(ciphertext.len() - 10);
230
231        let result = decrypt(&ciphertext, &key, &nonce);
232        assert!(result.is_err());
233        assert!(matches!(result, Err(EncryptionError::DecryptionFailed)));
234    }
235}