celerix_store/engine/
vault.rs

1use aes_gcm::{
2    aead::{Aead, AeadCore, KeyInit, OsRng},
3    Aes256Gcm, Nonce,
4};
5use crate::{Result, Error};
6
7/// Encrypts a plaintext string using AES-256-GCM and a 32-byte key.
8/// 
9/// Returns a hex-encoded string containing the nonce followed by the ciphertext.
10pub fn encrypt(plaintext: &str, key: &[u8]) -> Result<String> {
11    if key.len() != 32 {
12        return Err(Error::Internal("Key must be 32 bytes".to_string()));
13    }
14    let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| Error::Internal(e.to_string()))?;
15    let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96 bits / 12 bytes
16    let ciphertext = cipher.encrypt(&nonce, plaintext.as_bytes()).map_err(|e| Error::Internal(e.to_string()))?;
17
18    let mut combined = nonce.to_vec();
19    combined.extend_from_slice(&ciphertext);
20    Ok(hex::encode(combined))
21}
22
23/// Decrypts a hex-encoded ciphertext string using AES-256-GCM and a 32-byte key.
24/// 
25/// The `cipher_hex` must be the output of [`encrypt`], containing the 12-byte
26/// nonce followed by the ciphertext.
27pub fn decrypt(cipher_hex: &str, key: &[u8]) -> Result<String> {
28    if key.len() != 32 {
29        return Err(Error::Internal("Key must be 32 bytes".to_string()));
30    }
31    let combined = hex::decode(cipher_hex).map_err(|e| Error::Internal(e.to_string()))?;
32    if combined.len() < 12 {
33        return Err(Error::Internal("Ciphertext too short".to_string()));
34    }
35
36    let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| Error::Internal(e.to_string()))?;
37    let (nonce_bytes, ciphertext) = combined.split_at(12);
38    let nonce = Nonce::from_slice(nonce_bytes);
39
40    let plaintext_bytes = cipher.decrypt(nonce, ciphertext).map_err(|_| Error::Internal("decryption failed (wrong key or tampered data)".to_string()))?;
41    String::from_utf8(plaintext_bytes).map_err(|e| Error::Internal(e.to_string()))
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_encrypt_decrypt() {
50        let key = b"thisis32byteslongsecretkey123456";
51        let plaintext = "Hello, Celerix!";
52        let ciphertext = encrypt(plaintext, key).unwrap();
53        assert_ne!(ciphertext, plaintext);
54        let decrypted = decrypt(&ciphertext, key).unwrap();
55        assert_eq!(decrypted, plaintext);
56    }
57
58    #[test]
59    fn test_decrypt_with_wrong_key() {
60        let key1 = b"thisis32byteslongsecretkey123456";
61        let key2 = b"another32byteslongsecretkey65432";
62        let plaintext = "Secret message";
63        let ciphertext = encrypt(plaintext, key1).unwrap();
64        assert!(decrypt(&ciphertext, key2).is_err());
65    }
66}