1use aes_gcm::{
7 aead::{Aead, KeyInit},
8 Aes256Gcm, Nonce,
9};
10use zeroize::Zeroizing;
11
12use crate::error::CryptoError;
13use crate::random::generate_nonce;
14
15pub const KEY_SIZE: usize = 32;
17
18pub const NONCE_SIZE: usize = 12;
20
21pub const TAG_SIZE: usize = 16;
23
24pub fn encrypt(
39 key: &[u8],
40 plaintext: &[u8],
41 associated_data: Option<&[u8]>,
42) -> Result<Vec<u8>, CryptoError> {
43 if key.len() != KEY_SIZE {
44 return Err(CryptoError::InvalidKey(format!(
45 "expected {} bytes, got {}",
46 KEY_SIZE,
47 key.len()
48 )));
49 }
50
51 let cipher =
52 Aes256Gcm::new_from_slice(key).map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
53
54 let nonce_bytes = generate_nonce()?;
55 let nonce = Nonce::from_slice(&nonce_bytes);
56
57 let ciphertext = match associated_data {
58 Some(aad) => cipher
59 .encrypt(
60 nonce,
61 aes_gcm::aead::Payload {
62 msg: plaintext,
63 aad,
64 },
65 )
66 .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?,
67 None => cipher
68 .encrypt(nonce, plaintext)
69 .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?,
70 };
71
72 let mut result = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
73 result.extend_from_slice(&nonce_bytes);
74 result.extend_from_slice(&ciphertext);
75
76 Ok(result)
77}
78
79pub fn decrypt(
93 key: &[u8],
94 ciphertext: &[u8],
95 associated_data: Option<&[u8]>,
96) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
97 if key.len() != KEY_SIZE {
98 return Err(CryptoError::InvalidKey(format!(
99 "expected {} bytes, got {}",
100 KEY_SIZE,
101 key.len()
102 )));
103 }
104
105 if ciphertext.len() < NONCE_SIZE + TAG_SIZE {
106 return Err(CryptoError::InvalidInput(
107 "ciphertext too short".to_string(),
108 ));
109 }
110
111 let cipher =
112 Aes256Gcm::new_from_slice(key).map_err(|e| CryptoError::DecryptionFailed(e.to_string()))?;
113
114 let nonce = Nonce::from_slice(&ciphertext[..NONCE_SIZE]);
115 let encrypted = &ciphertext[NONCE_SIZE..];
116
117 let plaintext = match associated_data {
118 Some(aad) => cipher
119 .decrypt(
120 nonce,
121 aes_gcm::aead::Payload {
122 msg: encrypted,
123 aad,
124 },
125 )
126 .map_err(|_| CryptoError::DecryptionFailed("authentication failed".to_string()))?,
127 None => cipher
128 .decrypt(nonce, encrypted)
129 .map_err(|_| CryptoError::DecryptionFailed("authentication failed".to_string()))?,
130 };
131
132 Ok(Zeroizing::new(plaintext))
133}
134
135#[cfg(test)]
136#[allow(clippy::disallowed_methods)]
137mod tests {
138 use super::*;
139 use crate::random::generate_key;
140
141 #[test]
142 fn test_encrypt_decrypt_roundtrip() {
143 let key = generate_key().unwrap();
144 let plaintext = b"Hello, Egide!";
145
146 let ciphertext = encrypt(&*key, plaintext, None).unwrap();
147 let decrypted = decrypt(&*key, &ciphertext, None).unwrap();
148
149 assert_eq!(&*decrypted, plaintext);
150 }
151
152 #[test]
153 fn test_encrypt_decrypt_with_aad() {
154 let key = generate_key().unwrap();
155 let plaintext = b"secret data";
156 let aad = b"additional authenticated data";
157
158 let ciphertext = encrypt(&*key, plaintext, Some(aad)).unwrap();
159 let decrypted = decrypt(&*key, &ciphertext, Some(aad)).unwrap();
160
161 assert_eq!(&*decrypted, plaintext);
162 }
163
164 #[test]
165 fn test_decrypt_wrong_aad_fails() {
166 let key = generate_key().unwrap();
167 let plaintext = b"secret data";
168 let aad = b"correct aad";
169 let wrong_aad = b"wrong aad";
170
171 let ciphertext = encrypt(&*key, plaintext, Some(aad)).unwrap();
172 let result = decrypt(&*key, &ciphertext, Some(wrong_aad));
173
174 assert!(result.is_err());
175 }
176
177 #[test]
178 fn test_decrypt_wrong_key_fails() {
179 let key1 = generate_key().unwrap();
180 let key2 = generate_key().unwrap();
181 let plaintext = b"secret data";
182
183 let ciphertext = encrypt(&*key1, plaintext, None).unwrap();
184 let result = decrypt(&*key2, &ciphertext, None);
185
186 assert!(result.is_err());
187 }
188
189 #[test]
190 fn test_invalid_key_size() {
191 let short_key = vec![0u8; 16];
192 let plaintext = b"test";
193
194 let result = encrypt(&short_key, plaintext, None);
195 assert!(matches!(result, Err(CryptoError::InvalidKey(_))));
196 }
197
198 #[test]
199 fn test_ciphertext_format() {
200 let key = generate_key().unwrap();
201 let plaintext = b"test";
202
203 let ciphertext = encrypt(&*key, plaintext, None).unwrap();
204
205 assert_eq!(ciphertext.len(), NONCE_SIZE + plaintext.len() + TAG_SIZE);
206 }
207
208 #[test]
209 fn test_tampered_ciphertext_fails() {
210 let key = generate_key().unwrap();
211 let plaintext = b"secret data";
212
213 let mut ciphertext = encrypt(&*key, plaintext, None).unwrap();
214 ciphertext[NONCE_SIZE] ^= 0xFF;
215
216 let result = decrypt(&*key, &ciphertext, None);
217 assert!(result.is_err());
218 }
219}