anya_core/security/crypto/
symmetric.rs1use aes_gcm::{aead::Aead as AesAead, Aes256Gcm};
6use chacha20poly1305::{
8 aead::{KeyInit, Payload},
9 ChaCha20Poly1305, Key,
10};
11use thiserror::Error;
12
13use crate::security::crypto::random;
14
15#[derive(Debug, Error)]
17pub enum SymmetricError {
18 #[error("Encryption error: {0}")]
19 EncryptionError(String),
20
21 #[error("Decryption error: {0}")]
22 DecryptionError(String),
23
24 #[error("Invalid key error: {0}")]
25 InvalidKeyError(String),
26
27 #[error("Invalid data error: {0}")]
28 InvalidDataError(String),
29
30 #[error("Invalid nonce error: {0}")]
31 InvalidNonceError(String),
32
33 #[error("Other error: {0}")]
34 OtherError(String),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum SymmetricAlgorithm {
40 Aes256Gcm,
42 Aes256Cbc,
44 Aes256Ctr,
46 ChaCha20Poly1305,
48}
49
50#[derive(Debug)]
52pub struct SymmetricCrypto {
53 algorithm: SymmetricAlgorithm,
55}
56
57impl SymmetricCrypto {
58 pub fn new(algorithm: SymmetricAlgorithm) -> Self {
60 Self { algorithm }
61 }
62
63 pub fn generate_key(&self) -> Vec<u8> {
65 match self.algorithm {
66 SymmetricAlgorithm::Aes256Gcm
67 | SymmetricAlgorithm::Aes256Cbc
68 | SymmetricAlgorithm::Aes256Ctr => {
69 random::random_bytes(32)
71 }
72 SymmetricAlgorithm::ChaCha20Poly1305 => {
73 random::random_bytes(32)
75 }
76 }
77 }
78
79 pub fn generate_nonce(&self) -> Vec<u8> {
81 match self.algorithm {
82 SymmetricAlgorithm::Aes256Gcm => {
83 random::random_bytes(12)
85 }
86 SymmetricAlgorithm::Aes256Cbc => {
87 random::random_bytes(16)
89 }
90 SymmetricAlgorithm::Aes256Ctr => {
91 random::random_bytes(16)
93 }
94 SymmetricAlgorithm::ChaCha20Poly1305 => {
95 random::random_bytes(12)
97 }
98 }
99 }
100
101 pub fn encrypt(
103 &self,
104 key: &[u8],
105 nonce: &[u8],
106 plaintext: &[u8],
107 aad: Option<&[u8]>,
108 ) -> Result<Vec<u8>, SymmetricError> {
109 match self.algorithm {
110 SymmetricAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(key, nonce, plaintext, aad),
111 SymmetricAlgorithm::ChaCha20Poly1305 => {
112 self.encrypt_chacha20_poly1305(key, nonce, plaintext, aad)
113 }
114 _ => Err(SymmetricError::EncryptionError(format!(
115 "Algorithm {:?} not yet implemented",
116 self.algorithm
117 ))),
118 }
119 }
120
121 pub fn decrypt(
123 &self,
124 key: &[u8],
125 nonce: &[u8],
126 ciphertext: &[u8],
127 aad: Option<&[u8]>,
128 ) -> Result<Vec<u8>, SymmetricError> {
129 match self.algorithm {
130 SymmetricAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(key, nonce, ciphertext, aad),
131 SymmetricAlgorithm::ChaCha20Poly1305 => {
132 self.decrypt_chacha20_poly1305(key, nonce, ciphertext, aad)
133 }
134 _ => Err(SymmetricError::DecryptionError(format!(
135 "Algorithm {:?} not yet implemented",
136 self.algorithm
137 ))),
138 }
139 }
140
141 fn encrypt_aes_gcm(
143 &self,
144 key: &[u8],
145 nonce: &[u8],
146 plaintext: &[u8],
147 aad: Option<&[u8]>,
148 ) -> Result<Vec<u8>, SymmetricError> {
149 if key.len() != 32 {
151 return Err(SymmetricError::InvalidKeyError(format!(
152 "AES-256-GCM requires a 32-byte key, got {}",
153 key.len()
154 )));
155 }
156
157 if nonce.len() != 12 {
158 return Err(SymmetricError::InvalidNonceError(format!(
159 "AES-256-GCM requires a 12-byte nonce, got {}",
160 nonce.len()
161 )));
162 }
163
164 let cipher = Aes256Gcm::new_from_slice(key)
166 .map_err(|e| SymmetricError::EncryptionError(e.to_string()))?;
167
168 let nonce = aes_gcm::Nonce::from_slice(nonce);
170
171 let payload = if let Some(aad_data) = aad {
173 aes_gcm::aead::Payload {
174 msg: plaintext,
175 aad: aad_data,
176 }
177 } else {
178 aes_gcm::aead::Payload {
179 msg: plaintext,
180 aad: &[],
181 }
182 };
183
184 cipher
185 .encrypt(nonce, payload)
186 .map_err(|e| SymmetricError::EncryptionError(e.to_string()))
187 }
188
189 fn decrypt_aes_gcm(
191 &self,
192 key: &[u8],
193 nonce: &[u8],
194 ciphertext: &[u8],
195 aad: Option<&[u8]>,
196 ) -> Result<Vec<u8>, SymmetricError> {
197 if key.len() != 32 {
199 return Err(SymmetricError::InvalidKeyError(format!(
200 "AES-256-GCM requires a 32-byte key, got {}",
201 key.len()
202 )));
203 }
204
205 if nonce.len() != 12 {
206 return Err(SymmetricError::InvalidNonceError(format!(
207 "AES-256-GCM requires a 12-byte nonce, got {}",
208 nonce.len()
209 )));
210 }
211
212 let cipher = Aes256Gcm::new_from_slice(key)
214 .map_err(|e| SymmetricError::DecryptionError(e.to_string()))?;
215
216 let nonce = aes_gcm::Nonce::from_slice(nonce);
218
219 let payload = if let Some(aad_data) = aad {
221 aes_gcm::aead::Payload {
222 msg: ciphertext,
223 aad: aad_data,
224 }
225 } else {
226 aes_gcm::aead::Payload {
227 msg: ciphertext,
228 aad: &[],
229 }
230 };
231
232 cipher
233 .decrypt(nonce, payload)
234 .map_err(|e| SymmetricError::DecryptionError(e.to_string()))
235 }
236
237 fn encrypt_chacha20_poly1305(
239 &self,
240 key: &[u8],
241 nonce: &[u8],
242 plaintext: &[u8],
243 aad: Option<&[u8]>,
244 ) -> Result<Vec<u8>, SymmetricError> {
245 if key.len() != 32 {
247 return Err(SymmetricError::InvalidKeyError(format!(
248 "ChaCha20-Poly1305 requires a 32-byte key, got {}",
249 key.len()
250 )));
251 }
252
253 if nonce.len() != 12 {
254 return Err(SymmetricError::InvalidNonceError(format!(
255 "ChaCha20-Poly1305 requires a 12-byte nonce, got {}",
256 nonce.len()
257 )));
258 }
259
260 let key = Key::from_slice(key);
262 let cipher = ChaCha20Poly1305::new(key);
263
264 let nonce = chacha20poly1305::Nonce::from_slice(nonce);
266
267 let payload = if let Some(aad_data) = aad {
269 Payload {
270 msg: plaintext,
271 aad: aad_data,
272 }
273 } else {
274 Payload {
275 msg: plaintext,
276 aad: &[],
277 }
278 };
279
280 cipher
281 .encrypt(nonce, payload)
282 .map_err(|e| SymmetricError::EncryptionError(e.to_string()))
283 }
284
285 fn decrypt_chacha20_poly1305(
287 &self,
288 key: &[u8],
289 nonce: &[u8],
290 ciphertext: &[u8],
291 aad: Option<&[u8]>,
292 ) -> Result<Vec<u8>, SymmetricError> {
293 if key.len() != 32 {
295 return Err(SymmetricError::InvalidKeyError(format!(
296 "ChaCha20-Poly1305 requires a 32-byte key, got {}",
297 key.len()
298 )));
299 }
300
301 if nonce.len() != 12 {
302 return Err(SymmetricError::InvalidNonceError(format!(
303 "ChaCha20-Poly1305 requires a 12-byte nonce, got {}",
304 nonce.len()
305 )));
306 }
307
308 let key = Key::from_slice(key);
310 let cipher = ChaCha20Poly1305::new(key);
311
312 let nonce = chacha20poly1305::Nonce::from_slice(nonce);
314
315 let payload = if let Some(aad_data) = aad {
317 Payload {
318 msg: ciphertext,
319 aad: aad_data,
320 }
321 } else {
322 Payload {
323 msg: ciphertext,
324 aad: &[],
325 }
326 };
327
328 cipher
329 .decrypt(nonce, payload)
330 .map_err(|e| SymmetricError::DecryptionError(e.to_string()))
331 }
332}
333
334pub fn create_aes_256_gcm() -> SymmetricCrypto {
336 SymmetricCrypto::new(SymmetricAlgorithm::Aes256Gcm)
337}
338
339pub fn create_chacha20_poly1305() -> SymmetricCrypto {
341 SymmetricCrypto::new(SymmetricAlgorithm::ChaCha20Poly1305)
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_aes_gcm() -> Result<(), Box<dyn std::error::Error>> {
350 let crypto = SymmetricCrypto::new(SymmetricAlgorithm::Aes256Gcm);
351
352 let key = crypto.generate_key();
354 let nonce = crypto.generate_nonce();
355
356 let plaintext = b"This is a test message";
358 let aad = b"Additional authenticated data";
359
360 let ciphertext = crypto.encrypt(&key, &nonce, plaintext, Some(aad))?;
362
363 assert_ne!(&ciphertext, plaintext);
365
366 let decrypted = crypto.decrypt(&key, &nonce, &ciphertext, Some(aad))?;
368
369 assert_eq!(&decrypted, plaintext);
371
372 let wrong_aad = b"Wrong additional data";
374 let result = crypto.decrypt(&key, &nonce, &ciphertext, Some(wrong_aad));
375 assert!(result.is_err());
376
377 Ok(())
378 }
379
380 #[test]
381 fn test_chacha20_poly1305() -> Result<(), Box<dyn std::error::Error>> {
382 let crypto = SymmetricCrypto::new(SymmetricAlgorithm::ChaCha20Poly1305);
383
384 let key = crypto.generate_key();
386 let nonce = crypto.generate_nonce();
387
388 let plaintext = b"This is a test message for ChaCha20-Poly1305";
390
391 let ciphertext = crypto.encrypt(&key, &nonce, plaintext, None)?;
393
394 assert_ne!(&ciphertext, plaintext);
396
397 let decrypted = crypto.decrypt(&key, &nonce, &ciphertext, None)?;
399
400 assert_eq!(&decrypted, plaintext);
402
403 Ok(())
404 }
405
406 #[test]
407 fn test_helper_functions() {
408 let aes_crypto = create_aes_256_gcm();
409 let chacha_crypto = create_chacha20_poly1305();
410
411 assert_eq!(aes_crypto.algorithm, SymmetricAlgorithm::Aes256Gcm);
412 assert_eq!(
413 chacha_crypto.algorithm,
414 SymmetricAlgorithm::ChaCha20Poly1305
415 );
416 }
417}