hefesto 1.1.0

Double envelope encryption — AES-256-GCM + Argon2id for multi-tenant applications
Documentation
use crate::error::{HefestoError, Result};
use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

pub(crate) fn hash_for_lookup(value: &str, tenant_key: &str) -> Result<String> {
    let mut mac = HmacSha256::new_from_slice(tenant_key.as_bytes())
        .map_err(|e| HefestoError::InvalidKey(e.to_string()))?;
    mac.update(value.as_bytes());
    Ok(hex::encode(mac.finalize().into_bytes()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deterministic() {
        let h1 = hash_for_lookup("user@example.com", "tenant_key").unwrap();
        let h2 = hash_for_lookup("user@example.com", "tenant_key").unwrap();
        assert_eq!(h1, h2);
    }

    #[test]
    fn different_values_different_hashes() {
        let h1 = hash_for_lookup("a@example.com", "key").unwrap();
        let h2 = hash_for_lookup("b@example.com", "key").unwrap();
        assert_ne!(h1, h2);
    }

    #[test]
    fn different_tenant_keys_different_hashes() {
        let h1 = hash_for_lookup("user@example.com", "tenant_a").unwrap();
        let h2 = hash_for_lookup("user@example.com", "tenant_b").unwrap();
        assert_ne!(h1, h2);
    }

    #[test]
    fn output_is_valid_hex() {
        let h = hash_for_lookup("test", "key").unwrap();
        assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
        assert_eq!(h.len(), 64);
    }
}