#![allow(deprecated)]
use aes_gcm::Aes256Gcm;
use aes_gcm::aead::generic_array::GenericArray;
use aes_gcm::aead::{Aead, KeyInit};
use anyhow::{Context, Result};
use base64::Engine;
use rand::Rng;
pub struct Crypt {
cipher: Aes256Gcm,
}
impl Crypt {
pub fn new(key: &[u8; 32]) -> Self {
let cipher = Aes256Gcm::new_from_slice(key).expect("valid 32-byte key");
Self { cipher }
}
pub fn from_key(key: &str) -> Result<Self> {
let encoded = key.strip_prefix("base64:").unwrap_or(key);
let bytes = base64::engine::general_purpose::STANDARD
.decode(encoded)
.context("Failed to decode APP_KEY")?;
let key_bytes: [u8; 32] = bytes
.as_slice()
.try_into()
.context("APP_KEY must be 32 bytes (base64-encoded)")?;
Ok(Self::new(&key_bytes))
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<String> {
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill(&mut nonce_bytes);
let nonce = *GenericArray::from_slice(&nonce_bytes);
let ciphertext = self
.cipher
.encrypt(&nonce, plaintext)
.map_err(|e| anyhow::anyhow!("Encryption failed: {e}"))?;
let mut combined = nonce_bytes.to_vec();
combined.extend_from_slice(&ciphertext);
Ok(base64::engine::general_purpose::STANDARD.encode(&combined))
}
pub fn decrypt(&self, encoded: &str) -> Result<Vec<u8>> {
let combined = base64::engine::general_purpose::STANDARD
.decode(encoded)
.context("Failed to decode ciphertext")?;
if combined.len() < 12 {
anyhow::bail!("Ciphertext too short");
}
let (nonce_bytes, ciphertext) = combined.split_at(12);
let nonce = *GenericArray::from_slice(nonce_bytes);
self.cipher
.decrypt(&nonce, ciphertext)
.map_err(|e| anyhow::anyhow!("Decryption failed: {e}"))
}
pub fn encrypt_value<T: serde::Serialize>(&self, value: &T) -> Result<String> {
let json = serde_json::to_vec(value).context("Serialisation failed")?;
self.encrypt(&json)
}
pub fn decrypt_value<T: serde::de::DeserializeOwned>(&self, encoded: &str) -> Result<T> {
let json = self.decrypt(encoded)?;
serde_json::from_slice(&json).context("Deserialisation failed")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_key() -> [u8; 32] {
let mut key = [0u8; 32];
rand::thread_rng().fill(&mut key);
key
}
#[test]
fn test_encrypt_decrypt() {
let crypt = Crypt::new(&test_key());
let encrypted = crypt.encrypt(b"hello world").unwrap();
let decrypted = crypt.decrypt(&encrypted).unwrap();
assert_eq!(decrypted, b"hello world");
}
#[test]
fn test_encrypt_decrypt_value() {
let crypt = Crypt::new(&test_key());
let encrypted = crypt.encrypt_value(&"secret message").unwrap();
let decrypted: String = crypt.decrypt_value(&encrypted).unwrap();
assert_eq!(decrypted, "secret message");
}
#[test]
fn test_different_keys_fail() {
let crypt_a = Crypt::new(&test_key());
let crypt_b = Crypt::new(&test_key());
let encrypted = crypt_a.encrypt(b"data").unwrap();
assert!(crypt_b.decrypt(&encrypted).is_err());
}
#[test]
fn test_from_key_string() {
let raw_key = test_key();
let encoded = format!(
"base64:{}",
base64::engine::general_purpose::STANDARD.encode(&raw_key)
);
let crypt = Crypt::from_key(&encoded).unwrap();
let encrypted = crypt.encrypt(b"test").unwrap();
assert_eq!(crypt.decrypt(&encrypted).unwrap(), b"test");
}
#[test]
fn test_encrypted_values_differ() {
let crypt = Crypt::new(&test_key());
let a = crypt.encrypt(b"same data").unwrap();
let b = crypt.encrypt(b"same data").unwrap();
assert_ne!(a, b); }
}