oxidite_security/
crypto.rs

1//! Cryptographic utilities
2
3use crate::{Result, SecurityError};
4use aes_gcm::{
5    aead::{Aead, KeyInit, OsRng},
6    Aes256Gcm, Nonce,
7};
8use aes_gcm::aead::rand_core::RngCore;
9use base64::{Engine as _, engine::general_purpose::STANDARD};
10
11/// AES-256-GCM encryption key
12pub struct AesKey {
13    cipher: Aes256Gcm,
14}
15
16impl AesKey {
17    /// Create a new key from bytes (must be 32 bytes)
18    pub fn from_bytes(key: &[u8]) -> Result<Self> {
19        if key.len() != 32 {
20            return Err(SecurityError::InvalidKeyLength);
21        }
22        
23        let cipher = Aes256Gcm::new_from_slice(key)
24            .map_err(|e| SecurityError::EncryptionError(e.to_string()))?;
25        
26        Ok(Self { cipher })
27    }
28
29    /// Generate a new random key
30    pub fn generate() -> Self {
31        let cipher = Aes256Gcm::new(&Aes256Gcm::generate_key(&mut OsRng));
32        Self { cipher }
33    }
34
35    /// Encrypt data
36    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
37        let mut nonce_bytes = [0u8; 12];
38        OsRng.fill_bytes(&mut nonce_bytes);
39        let nonce = Nonce::from_slice(&nonce_bytes);
40
41        let ciphertext = self.cipher
42            .encrypt(nonce, plaintext)
43            .map_err(|e| SecurityError::EncryptionError(e.to_string()))?;
44
45        // Prepend nonce to ciphertext
46        let mut result = Vec::with_capacity(12 + ciphertext.len());
47        result.extend_from_slice(&nonce_bytes);
48        result.extend(ciphertext);
49        
50        Ok(result)
51    }
52
53    /// Decrypt data
54    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
55        if ciphertext.len() < 12 {
56            return Err(SecurityError::InvalidFormat);
57        }
58
59        let (nonce_bytes, encrypted) = ciphertext.split_at(12);
60        let nonce = Nonce::from_slice(nonce_bytes);
61
62        self.cipher
63            .decrypt(nonce, encrypted)
64            .map_err(|e| SecurityError::DecryptionError(e.to_string()))
65    }
66}
67
68/// Encrypt data with a key (returns base64-encoded result)
69pub fn encrypt(key: &[u8], plaintext: &[u8]) -> Result<String> {
70    let aes_key = AesKey::from_bytes(key)?;
71    let encrypted = aes_key.encrypt(plaintext)?;
72    Ok(STANDARD.encode(&encrypted))
73}
74
75/// Decrypt base64-encoded data with a key
76pub fn decrypt(key: &[u8], ciphertext: &str) -> Result<Vec<u8>> {
77    let aes_key = AesKey::from_bytes(key)?;
78    let encrypted = STANDARD.decode(ciphertext)
79        .map_err(|_| SecurityError::InvalidFormat)?;
80    aes_key.decrypt(&encrypted)
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_encrypt_decrypt() {
89        let key = AesKey::generate();
90        let plaintext = b"Hello, World!";
91        
92        let encrypted = key.encrypt(plaintext).unwrap();
93        let decrypted = key.decrypt(&encrypted).unwrap();
94        
95        assert_eq!(decrypted, plaintext);
96    }
97
98    #[test]
99    fn test_convenience_functions() {
100        let key = [0u8; 32]; // 32-byte key
101        let plaintext = b"Secret message";
102        
103        let encrypted = encrypt(&key, plaintext).unwrap();
104        let decrypted = decrypt(&key, &encrypted).unwrap();
105        
106        assert_eq!(decrypted, plaintext);
107    }
108}