lc/sync/
encryption.rs

1//! AES256-GCM encryption/decryption functionality for configuration files
2
3use aes_gcm::{
4    aead::{Aead, AeadCore, KeyInit, OsRng},
5    Aes256Gcm, Key, Nonce,
6};
7use anyhow::Result;
8use base64::{engine::general_purpose, Engine as _};
9
10/// Derive a 256-bit key from a password using a simple approach
11/// In production, you might want to use PBKDF2, scrypt, or Argon2
12pub fn derive_key_from_password(password: &str) -> Result<[u8; 32]> {
13    use std::collections::hash_map::DefaultHasher;
14    use std::hash::{Hash, Hasher};
15
16    // Simple key derivation - in production, use proper KDF like PBKDF2
17    let mut hasher = DefaultHasher::new();
18    password.hash(&mut hasher);
19    let hash1 = hasher.finish();
20
21    // Create a second hash for more entropy
22    let mut hasher2 = DefaultHasher::new();
23    format!("{}{}", password, hash1).hash(&mut hasher2);
24    let hash2 = hasher2.finish();
25
26    // Combine hashes to create 32-byte key
27    let mut key = [0u8; 32];
28    key[0..8].copy_from_slice(&hash1.to_le_bytes());
29    key[8..16].copy_from_slice(&hash2.to_le_bytes());
30    key[16..24].copy_from_slice(&hash1.to_be_bytes());
31    key[24..32].copy_from_slice(&hash2.to_be_bytes());
32
33    Ok(key)
34}
35
36/// Encrypt data using AES256-GCM
37pub fn encrypt_data(data: &[u8], key: &[u8; 32]) -> Result<Vec<u8>> {
38    let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
39    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
40
41    let ciphertext = cipher
42        .encrypt(&nonce, data)
43        .map_err(|e| anyhow::anyhow!("Encryption failed: {}", e))?;
44
45    // Prepend nonce to ciphertext for storage
46    let mut result = Vec::with_capacity(nonce.len() + ciphertext.len());
47    result.extend_from_slice(&nonce);
48    result.extend_from_slice(&ciphertext);
49
50    Ok(result)
51}
52
53/// Decrypt data using AES256-GCM
54#[allow(dead_code)]
55pub fn decrypt_data(encrypted_data: &[u8], key: &[u8; 32]) -> Result<Vec<u8>> {
56    if encrypted_data.len() < 12 {
57        anyhow::bail!("Invalid encrypted data: too short");
58    }
59
60    let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
61
62    // Extract nonce and ciphertext
63    let (nonce_bytes, ciphertext) = encrypted_data.split_at(12);
64    let nonce = Nonce::from_slice(nonce_bytes);
65
66    let plaintext = cipher
67        .decrypt(nonce, ciphertext)
68        .map_err(|e| anyhow::anyhow!("Decryption failed: {}", e))?;
69
70    Ok(plaintext)
71}
72
73/// Encode binary data to base64 for safe transport/storage
74#[allow(dead_code)]
75pub fn encode_base64(data: &[u8]) -> String {
76    general_purpose::STANDARD.encode(data)
77}
78
79/// Decode base64 data back to binary
80#[allow(dead_code)]
81pub fn decode_base64(data: &str) -> Result<Vec<u8>> {
82    general_purpose::STANDARD
83        .decode(data)
84        .map_err(|e| anyhow::anyhow!("Base64 decode failed: {}", e))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_key_derivation() {
93        let password = "test_password_123";
94        let key1 = derive_key_from_password(password).unwrap();
95        let key2 = derive_key_from_password(password).unwrap();
96
97        // Same password should produce same key
98        assert_eq!(key1, key2);
99
100        // Different password should produce different key
101        let key3 = derive_key_from_password("different_password").unwrap();
102        assert_ne!(key1, key3);
103    }
104
105    #[test]
106    fn test_encryption_decryption() {
107        let data = b"Hello, World! This is test data for encryption.";
108        let password = "test_password_123";
109        let key = derive_key_from_password(password).unwrap();
110
111        // Encrypt
112        let encrypted = encrypt_data(data, &key).unwrap();
113        assert_ne!(encrypted.as_slice(), data);
114        assert!(encrypted.len() > data.len()); // Should be larger due to nonce + auth tag
115
116        // Decrypt
117        let decrypted = decrypt_data(&encrypted, &key).unwrap();
118        assert_eq!(decrypted.as_slice(), data);
119    }
120
121    #[test]
122    fn test_encryption_with_wrong_key() {
123        let data = b"Hello, World!";
124        let key1 = derive_key_from_password("password1").unwrap();
125        let key2 = derive_key_from_password("password2").unwrap();
126
127        let encrypted = encrypt_data(data, &key1).unwrap();
128
129        // Decryption with wrong key should fail
130        assert!(decrypt_data(&encrypted, &key2).is_err());
131    }
132
133    #[test]
134    fn test_base64_encoding() {
135        let data = b"Hello, World!";
136        let encoded = encode_base64(data);
137        let decoded = decode_base64(&encoded).unwrap();
138
139        assert_eq!(decoded.as_slice(), data);
140    }
141
142    #[test]
143    fn test_invalid_encrypted_data() {
144        let key = derive_key_from_password("test").unwrap();
145
146        // Too short data should fail
147        assert!(decrypt_data(b"short", &key).is_err());
148
149        // Invalid data should fail
150        let invalid_data = vec![0u8; 20];
151        assert!(decrypt_data(&invalid_data, &key).is_err());
152    }
153}