Skip to main content

envvault/crypto/
keys.rs

1//! Key derivation helpers using HKDF-SHA256.
2//!
3//! From a single master key we derive:
4//! - A unique **per-secret** encryption key for each secret name.
5//! - A dedicated **HMAC key** for vault integrity checks.
6//!
7//! HKDF (RFC 5869) uses the master key as input keying material (IKM)
8//! and a context string (`info`) to produce independent sub-keys.
9
10use hkdf::Hkdf;
11use sha2::Sha256;
12use zeroize::Zeroize;
13
14use crate::errors::{EnvVaultError, Result};
15
16/// Length of derived sub-keys (256 bits).
17const KEY_LEN: usize = 32;
18
19/// Derive a per-secret encryption key from the master key.
20///
21/// Each secret name produces a different key so that compromising one
22/// encrypted value does not reveal others.
23///
24/// `info` is set to `"envvault-secret:<secret_name>"` to bind the
25/// derived key to a specific secret.
26pub fn derive_secret_key(master_key: &[u8], secret_name: &str) -> Result<[u8; KEY_LEN]> {
27    let info = format!("envvault-secret:{secret_name}");
28    hkdf_derive(master_key, info.as_bytes())
29}
30
31/// Derive an HMAC key from the master key.
32///
33/// This key is used to compute an HMAC over the vault file so we can
34/// detect tampering before attempting decryption.
35pub fn derive_hmac_key(master_key: &[u8]) -> Result<[u8; KEY_LEN]> {
36    hkdf_derive(master_key, b"envvault-hmac-key")
37}
38
39/// Internal helper: run HKDF-SHA256 expand with the given `info`.
40///
41/// We skip the `extract` step and use the master key directly as the
42/// pseudo-random key (PRK), because the master key already has high
43/// entropy (it came from Argon2id).
44fn hkdf_derive(ikm: &[u8], info: &[u8]) -> Result<[u8; KEY_LEN]> {
45    // `salt` is None — HKDF will use a zero-filled salt internally.
46    let hk = Hkdf::<Sha256>::new(None, ikm);
47
48    let mut okm = [0u8; KEY_LEN];
49    hk.expand(info, &mut okm)
50        .map_err(|e| EnvVaultError::KeyDerivationFailed(format!("HKDF expand failed: {e}")))?;
51
52    Ok(okm)
53}
54
55/// A wrapper around a 32-byte master key that automatically zeroes
56/// its memory when dropped.
57///
58/// Use this to hold the master key in memory so it cannot linger
59/// after it is no longer needed.
60#[derive(Zeroize)]
61#[zeroize(drop)]
62pub struct MasterKey {
63    bytes: [u8; KEY_LEN],
64}
65
66impl MasterKey {
67    /// Create a new `MasterKey` from raw bytes.
68    pub fn new(bytes: [u8; KEY_LEN]) -> Self {
69        Self { bytes }
70    }
71
72    /// Access the raw key bytes (e.g. to pass to HKDF or encryption).
73    pub fn as_bytes(&self) -> &[u8; KEY_LEN] {
74        &self.bytes
75    }
76
77    /// Derive a per-secret encryption key from this master key.
78    pub fn derive_secret_key(&self, secret_name: &str) -> Result<[u8; KEY_LEN]> {
79        derive_secret_key(&self.bytes, secret_name)
80    }
81
82    /// Derive an HMAC key from this master key.
83    pub fn derive_hmac_key(&self) -> Result<[u8; KEY_LEN]> {
84        derive_hmac_key(&self.bytes)
85    }
86}