use aes_gcm::aead::{Aead, KeyInit}; use aes_gcm::{Aes256Gcm, Nonce}; use anyhow::{Context, Result}; use rand::Rng; pub const KEY_CHECK_PLAINTEXT: &[u8] = b"secrets-v3-key-check"; pub fn decode_hex(input: &str) -> Result> { hex::decode(input.trim()).context("invalid hex") } pub fn encode_hex(input: &[u8]) -> String { hex::encode(input) } pub fn extract_key_32(input: &str) -> Result<[u8; 32]> { let bytes = decode_hex(input)?; let key: [u8; 32] = bytes .try_into() .map_err(|_| anyhow::anyhow!("expected 32-byte key"))?; Ok(key) } pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result> { let cipher = Aes256Gcm::new_from_slice(key).context("invalid AES-256 key")?; let mut nonce_bytes = [0_u8; 12]; rand::rng().fill_bytes(&mut nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes); let mut out = nonce_bytes.to_vec(); out.extend( cipher .encrypt(nonce, plaintext) .map_err(|_| anyhow::anyhow!("encryption failed"))?, ); Ok(out) } pub fn decrypt(key: &[u8; 32], ciphertext: &[u8]) -> Result> { if ciphertext.len() < 12 { anyhow::bail!("ciphertext too short"); } let cipher = Aes256Gcm::new_from_slice(key).context("invalid AES-256 key")?; let (nonce, body) = ciphertext.split_at(12); cipher .decrypt(Nonce::from_slice(nonce), body) .map_err(|_| anyhow::anyhow!("decryption failed")) }