What is Salted Challenge Response Authentication Mechanism?

SCRAM authenticates users without sending passwords, stores only derived keys (not plaintext), and prevents replay attacks with nonces and salts. It’s a modern alternative to legacy password schemes and is available via SASL in many servers and clients.

What Is SCRAM?

Salted Challenge Response Authentication Mechanism (SCRAM) is a password-based authentication protocol standardized by the IETF (commonly used as a SASL mechanism). Instead of transmitting the user’s password, SCRAM proves knowledge of it through a challenge-response exchange using:

  • a salt (unique per account),
  • a nonce (unique per session),
  • an iteration count (work factor),
  • and a key-derivation function (e.g., PBKDF2 with HMAC-SHA-256).

Common variants: SCRAM-SHA-1, SCRAM-SHA-256, and SCRAM-SHA-512 (some deployments also use channel binding for MITM protection).

How SCRAM Works (Step-by-Step)

Notation: H() = hash (e.g., SHA-256), HMAC(k,m), KDF(password, salt, iterations) = PBKDF2-HMAC.

  1. Client → Server: client-first-message
    Sends username and a fresh client nonce nc.
  2. Server → Client: server-first-message
    Looks up user’s stored auth data, returns:
    • salt s (from account record),
    • iteration count i,
    • server nonce ns (fresh, often concatenated with nc).
  3. Client computes keys locally
    • SaltedPassword = KDF(password, s, i)
    • ClientKey = HMAC(SaltedPassword, "Client Key")
    • StoredKey = H(ClientKey)
    • Builds an auth message transcript (the exact strings of the three messages).
  4. Client → Server: client-final-message
    Sends:
    • combined nonce (nc+ns),
    • ClientProof = ClientKey XOR HMAC(StoredKey, AuthMessage)
      (This proves the client knows the password without sending it.)
  5. Server verifies
    • Recomputes StoredKey from its stored data, verifies ClientProof.
    • If valid, computes ServerKey = HMAC(SaltedPassword, "Server Key") and
      ServerSignature = HMAC(ServerKey, AuthMessage).
  6. Server → Client: server-final-message
    Returns ServerSignature so the client can verify it’s talking to the real server.

What the server stores: never the plaintext password. It stores salt, iteration count, and either the SaltedPassword or the derived StoredKey and ServerKey (or values sufficient to recompute/verify them).

Main Features & Components

  • Salting: Unique per-user salt thwarts rainbow tables.
  • Key Derivation with Work Factor: Iterations make brute force slower.
  • Challenge-Response with Nonces: Prevents replay attacks.
  • Mutual Authentication: Client verifies the server via ServerSignature.
  • No Plaintext Passwords in Transit or at Rest: Only derived values are stored/transmitted.
  • Channel Binding (optional): Binds auth to the underlying TLS channel to deter MITM.

Benefits & Advantages

  • Strong security with passwords: Better than Basic/Digest/PLAIN (without TLS).
  • Minimal leakage if DB is stolen: Attackers get salts and derived keys, not plaintext.
  • Replay-resistant: Nonces and signed transcripts block replays.
  • Standards-based & widely supported: Kafka, PostgreSQL, MongoDB, IMAP/SMTP, XMPP, LDAP, etc.
  • No PKI dependency: Works with or without TLS (though TLS is strongly recommended).

When & How to Use SCRAM

Use SCRAM when you:

  • need password-based auth with solid defenses (microservices, message brokers, DBs),
  • require mutual verification (client also verifies server),
  • want a drop-in option supported by SASL frameworks and libraries.

Pair it with TLS in any hostile network. Prefer SCRAM-SHA-256 or stronger. Enable channel binding where client/server stacks support it.

Real-World Use Cases

  • Message brokers: Kafka clusters using SASL/SCRAM for client-to-broker auth.
  • Databases: PostgreSQL and MongoDB deployments using SCRAM-SHA-256.
  • Email/XMPP/LDAP: SASL SCRAM to avoid password exposure and replays.
  • Enterprise gateways: Reverse proxies terminating TLS and relaying SCRAM to backends.

Implementation Blueprint (Server-Side)

Account creation / password change

  • Generate random salt (16–32 bytes).
  • Choose iterations (e.g., 65,536+; tune for latency).
  • Compute SaltedPassword = KDF(password, salt, iterations).
  • Derive and store either:
    • StoredKey = H(HMAC(SaltedPassword, "Client Key"))
    • ServerKey = HMAC(SaltedPassword, "Server Key")
    • plus salt, iterations, username
    • (Optionally store SaltedPassword if your library expects it, but avoid storing plaintext or unsalted hashes.)

Authentication flow (pseudocode)

# server-first-message
record = lookup(username)
nonce_s  = random()
send { salt: record.salt, iter: record.iter, nonce: client_nonce + nonce_s }

# client-final-message arrives with ClientProof and combined nonce
authMsg = transcript(clientFirst, serverFirst, clientFinalWithoutProof)

# Verify proof
ClientSignature = HMAC(record.StoredKey, authMsg)
ClientKey = XOR(ClientProof, ClientSignature)
StoredKey' = H(ClientKey)
if StoredKey' != record.StoredKey: reject

# Success: send server signature for mutual auth
ServerSignature = HMAC(record.ServerKey, authMsg)
return { server_signature: ServerSignature }

Storage schema (example)

users(
  id PK,
  username UNIQUE,
  salt VARBINARY(32),
  iterations INT,
  stored_key VARBINARY(32 or 64),
  server_key VARBINARY(32 or 64),
  updated_at TIMESTAMP
)

Security Best Practices

  • Always use TLS, and enable channel binding if your stack supports it.
  • Strong randomness for salts and nonces (CSPRNG).
  • High iteration counts tuned to your latency budget; revisit yearly.
  • Rate-limit and lockout policies to deter online guessing.
  • Audit and rotate credentials; support password upgrades (e.g., SHA-1 → SHA-256).
  • Side-channel hygiene: constant-time comparisons; avoid verbose error messages.

Integrating SCRAM into Your Software Development Process

1) Design & Requirements

  • Decide on algorithm (prefer SCRAM-SHA-256 or higher) and iterations.
  • Define migration plan from existing auth (fallback or forced reset).

2) Implementation

  • Use a well-maintained SASL/SCRAM library for your language/runtime.
  • Centralize KDF and nonce/salt generation utilities.
  • Add feature flags to switch mechanisms and iteration counts.

3) Configuration & DevOps

  • Store salts/keys only in your DB; protect backups.
  • Secrets (e.g., TLS keys) in a vault; enforce mTLS between services where applicable.
  • Add dashboards for auth failures, lockouts, and latency.

4) Testing

  • Unit-test transcripts against known vectors from your library/docs.
  • Property/fuzz tests for parser edge cases (attribute order, malformed messages).
  • Integration tests with TLS on/off, and with channel binding if used.

5) Rollout

  • Canary a subset of users/services.
  • Monitor failure rates and latency; adjust iterations if needed.
  • Backfill/migrate user records on next login or via scheduled jobs.

Comparison Cheat Sheet

MechanismSends Password?Server StoresReplay-ResistantMutual AuthNotes
Basic (over TLS)Yes (base64)Plain/Hash (app-defined)NoNoOnly acceptable with strong TLS; still weak vs replays if tokens leak.
DigestNoHash of passwordPartiallyNoOutdated; weaker KDF and known issues.
PLAIN (over TLS)YesApp-definedNoNoOnly safe inside TLS; still exposes password at app layer.
SCRAMNoSalted keysYesYesModern default for password auth; supports channel binding.
OAuth 2.0/OIDCN/ATokensYesYes (via TLS + signatures)Token-based; different tradeoffs and flow.

Developer Quick-Start (Language-Agnostic)

  • Pick a library that supports SCRAM-SHA-256 and (if possible) channel binding.
  • Server config: enable the SCRAM mechanism; set minimum iteration count and required hash.
  • Client config: select SCRAM mechanism; supply username/password; verify server signature.
  • Migrations: on user login, if you detect an old scheme (e.g., SHA-1), re-derive keys with SHA-256 and higher iterations and update the record.

FAQs

Do I still need TLS with SCRAM?
Yes. SCRAM protects passwords and gives mutual auth, but TLS protects confidentiality/integrity of all data and enables channel binding.

Which hash should I choose?
Use SCRAM-SHA-256 or stronger. Avoid SHA-1 for new systems.

How many iterations?
Start with a value that adds ~50–150 ms on your hardware per attempt, then adjust based on throughput/latency targets.

Final Checklist

  • SCRAM-SHA-256 enabled on server and clients
  • Unique salt per user, secure CSPRNG
  • Iterations set and documented; metrics in place
  • TLS enforced; channel binding on where supported
  • Tests cover transcripts, edge cases, and migrations
  • Monitoring, rate-limiting, and lockouts configured