AkurAI Build
Menu

Public repository / 653879bcf622c47638f5aab413ebab46937365ab

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}")))
    }
}