Menu
Public repository / 0530b09e3daf6df75da3076c9559324ed6fbbc16
AkurAI-PassVault / crates/passvault-core/src/crypto.rs
Latest change 653879bcf622c47638f5aab413ebab46937365ab - Initial: AkurAI-PassVault AES-256-GCM encrypted vault with MCP API by Claude Code
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use crate::error::Error;
pub struct Crypto {
cipher: Aes256Gcm,
}
impl Crypto {
pub fn from_hex_key(hex_key: &str) -> Result<Self, Error> {
let key_bytes = hex::decode(hex_key)
.map_err(|e| Error::Crypto(format!("invalid hex key: {e}")))?;
if key_bytes.len() != 32 {
return Err(Error::Crypto("key must be 32 bytes (64 hex chars)".into()));
}
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&key_bytes);
let cipher = Aes256Gcm::new(key);
Ok(Crypto { cipher })
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce_bytes: [u8; 12] = {
let mut buf = [0u8; 12];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut buf);
buf
};
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = self.cipher.encrypt(nonce, plaintext)
.map_err(|e| Error::Crypto(format!("encryption failed: {e}")))?;
let mut result = Vec::with_capacity(12 + ciphertext.len());
result.extend_from_slice(&nonce_bytes);
result.extend_from_slice(&ciphertext);
Ok(result)
}
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
if data.len() < 12 {
return Err(Error::Crypto("data too short".into()));
}
let (nonce_bytes, ciphertext) = data.split_at(12);
let nonce = Nonce::from_slice(nonce_bytes);
self.cipher.decrypt(nonce, ciphertext)
.map_err(|e| Error::Crypto(format!("decryption failed: {e}")))
}
}