hsh-kms 0.0.10

Pepper / KMS integration for the hsh crate: HMAC-SHA-256 pepper application with versioned keys and pluggable KMS backends.
Documentation

Contents

Install · Concepts · Quick Start · Provider matrix · Rotation playbook · Threat model · Examples · Documentation · License


Install

[dependencies]
hsh-kms = "0.0.9"
hsh     = { version = "0.0.9", features = ["pepper"] }

MSRV 1.75 stable. no_std-friendly for LocalPepper; KMS providers require std + tokio.

Provider features

Feature Status Pulls in
default always LocalPepper + Pepper trait
aws-kms stub (future) aws-sdk-kms
gcp-kms stub (future) gcloud-kms
azure-key-vault stub (future) azure_security_keyvault
hashicorp-vault stub (future) vaultrs

Provider features today expose the stable FetchOpts shape and a fetch_pepper stub that returns PepperError::Backend("not yet wired up"). Real implementations land incrementally as integration-test infrastructure (localstack / cloud-mock containers) comes online.


Concepts

Concept What it is
Pepper A server-side secret applied to every password before it is hashed. Lives in a KMS / HSM, separate from the password database.
Pepper trait Sync interface producing HMAC-SHA-256(key_at(version), password) → 32-byte tag.
KeyVersion Monotonically increasing u32 carried alongside each peppered hash. Makes rotation non-destructive.
LocalPepper In-memory pepper provider. Use for tests, short-lived workloads, or pin in-process secrets at startup.
FetchOpts Per-provider options struct (aws::FetchOpts, gcp::FetchOpts, etc.) carrying the KMS key reference + encrypted key versions.

Full design rationale: ADR-0003 — Pepper key-versioning scheme.


Quick Start

use hsh_kms::{KeyVersion, LocalPepper, Pepper};

# fn main() {
let pepper = LocalPepper::builder()
    .add(KeyVersion::new(1), b"v1-server-pepper-32-bytes-min!!!".to_vec())
    .current(KeyVersion::new(1))
    .build()
    .unwrap();

let tag = pepper.apply(KeyVersion::new(1), b"correct horse").unwrap();
assert_eq!(tag.len(), 32);
# }

Then attach to a hsh policy:

use std::sync::Arc;
use hsh::{api, Policy};
use hsh_kms::{KeyVersion, LocalPepper};

# fn main() -> Result<(), hsh::Error> {
let pepper = LocalPepper::builder()
    .add(KeyVersion::new(1), b"server-pepper-bytes-keep-secret!".to_vec())
    .current(KeyVersion::new(1))
    .build()
    .unwrap();

let policy = Policy::owasp_minimum_2025()
    .with_pepper(Arc::new(pepper));

let stored = api::hash(&policy, "user-password")?;
assert!(stored.starts_with("hsh-pepper:1:"));
# Ok(()) }

Provider matrix

Provider Module FetchOpts shape Status
In-memory [LocalPepper] builder API (add / current / build) ✅ live
AWS KMS [aws] key_id, per-version encrypted blobs, current 🚧 stub
GCP Cloud KMS [gcp] key_resource, per-version encrypted blobs, current 🚧 stub
Azure Key Vault [azure] vault_url, secret_name, per-version refs, current 🚧 stub
HashiCorp Vault [vault] address, mount, key_name, encrypted blobs 🚧 stub

The trait shape is stable. Stubs return PepperError::Backend("not yet wired up"); replace with real implementations as your deployment requires.


Rotation playbook

  1. Generate a fresh 32-byte pepper, register it as KeyVersion::new(N+1) in your KMS.
  2. Add it to the LocalPepper keyset alongside the existing versions — do not remove old versions yet.
  3. Bump current to N+1 and redeploy.
  4. As users log in, verify_and_upgrade returns Outcome::Valid { rehashed: Some(new_phc) } carrying keyver=N+1; persist new_phc.
  5. After a chosen window (e.g. 90 days), audit your DB for rows still on old keyvers. Force-rotate inactive users via fresh sign-in.
  6. Once no rows reference an old keyver, drop it from the keyset on the next deploy.

Full deployment guide: doc/KMS-INTEGRATION.md.


Threat model

Defends against

  • Offline brute force after a password-DB breach (attacker doesn't have the pepper).
  • Pepper-key compromise within a single rotation window (old hashes migrate transparently).

Does NOT defend against

  • KMS compromise (the pepper is in the same trust boundary as your KMS).
  • An attacker who can read both the password DB and execute code with the running app's privileges.
  • Online brute force — rate-limit your login endpoint separately.

For FIPS deployments where the pepper must never leave the HSM, see doc/FIPS.md.


Examples

See crates/hsh-kms/examples/ for runnable demos:

  • local_pepper.rs — build a LocalPepper and apply it.
  • rotation.rs — multi-version keyset + rotation simulation.
  • refuse_without_pepper.rs — fail-closed behaviour demo.

Run with cargo run -p hsh-kms --example local_pepper.


Documentation

Doc What's in it
adr/0003-pepper-key-versioning.md Storage format, rotation contract, fail-closed rationale
KMS-INTEGRATION.md AWS / GCP / Azure / Vault deployment guides
SECURITY.md Vulnerability reporting

License

Dual-licensed under Apache 2.0 or MIT, at your option.