← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Password Peppering

Security OWASP A2:2021 PHP 5.5+ Intermediate
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate semgrep can flag absence of pepper patterns, but the automated field is 'no' — semgrep rules require manual configuration and pattern definition for something as conceptually nuanced as 'password_hash without pepper'. The real failure (e.g. pepper stored in DB, hardcoded in source) requires careful code review to spot. No standard linter catches this by default.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes prepending a pepper from env vars, which sounds simple, but common_mistakes include pepper rotation (requires re-hashing all passwords at next login) and correcting misuse (e.g. moving a pepper from DB to env, fixing HMAC ordering). This touches authentication flows, environment configuration, and potentially deployment pipelines — more than a single-line swap but not a full architectural rework.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). Peppering applies to the web authentication context broadly and imposes ongoing operational concerns: pepper must be stored securely outside the DB, rotated carefully, and every developer touching auth must understand the pattern. It shapes how password hashing is done across the codebase but does not define the entire system's architecture — closer to b5 than b7.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field explicitly identifies the core trap: developers assume a pepper in config is functionally equivalent to a salt in the DB, misunderstanding that the pepper's value comes from being stored *outside* the database. Common mistakes reinforce this — storing pepper in the DB, hardcoding it in version-controlled source, or misapplying HMAC ordering. These are plausible mistakes a competent developer would make without prior exposure to peppering.

About DEBT scoring →

Also Known As

pepper server-side secret secret salt

TL;DR

A secret server-side value mixed into passwords before hashing — database theft alone is insufficient; the pepper must also be compromised.

Explanation

A pepper is a secret value (distinct from a salt) that is the same for all users, stored outside the database (environment variable, secrets manager, HSM) and concatenated with the password before hashing. If an attacker steals the database, they have hashes and salts but not the pepper — offline cracking is impossible without it. In PHP, prepend or HMAC the pepper before password_hash(): $peppered = hash_hmac('sha256', $password, $pepper); password_hash($peppered, PASSWORD_ARGON2ID). Rotate peppers by re-hashing on next login.

Watch Out

Never store the pepper in the database alongside the hashes — it must live in a separate, access-controlled location to provide any benefit.

Common Misconception

A pepper stored in config is as secure as a salt stored in the database. That is the point — a pepper is a server-side secret that remains useful even if the database is fully dumped, since cracking requires both the hash and the pepper.

Why It Matters

A pepper is a secret added to passwords before hashing — even if the database is stolen, the attacker cannot crack hashes without also compromising the pepper stored outside the database.

Common Mistakes

  • Storing the pepper in the database alongside the hashes — defeats the entire purpose.
  • Using a hardcoded pepper in source code that is committed to version control.
  • Using HMAC-then-hash rather than hash(password + pepper) — the order and method matters for security properties.
  • Not planning for pepper rotation — changing the pepper requires re-hashing all passwords at next login.

Code Examples

💡 Note
Pepper + bcrypt/Argon2id is defence in depth: bcrypt protects against brute force; pepper protects against DB dumps.
✗ Vulnerable
// Pepper stored in the same DB as hashes:
$pepper = $db->query('SELECT value FROM config WHERE key = "pepper"');
$hash = password_hash($password . $pepper, PASSWORD_BCRYPT);
// Database breach reveals both the hash and the pepper
✓ Fixed
// Pepper is an application-level secret added before hashing
// Unlike salt (stored in DB), pepper is in environment config
// A compromised DB dump is useless without the pepper

$pepper  = $_ENV['PASSWORD_PEPPER']; // e.g. 64-char random string
$peppered = hash_hmac('sha256', $plaintext, $pepper);
$hash     = password_hash($peppered, PASSWORD_ARGON2ID);

// Verify
$peppered = hash_hmac('sha256', $input, $pepper);
if (password_verify($peppered, $storedHash)) { /* authenticated */ }

// Rotate pepper: add new pepper env var, re-hash on next successful login

Added 15 Mar 2026
Edited 22 Mar 2026
Views 35
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 1 ping T 0 pings W 0 pings T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 10 Unknown AI 4 Ahrefs 4 ChatGPT 4 Google 2 Perplexity 2 Claude 2 SEMrush 2 Scrapy 2 Meta AI 1 PetalBot 1
crawler 29 crawler_json 4 pre-tracking 1
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Prepend a secret pepper (from env vars, not the database) to passwords before hashing — if the DB leaks, cracking hashes requires the pepper too; rotate by re-hashing on next login
📦 Applies To
PHP 5.5+ web
🔗 Prerequisites
🔍 Detection Hints
password_hash without pepper; only bcrypt no additional secret; pepper stored in DB alongside password hash
Auto-detectable: ✗ No semgrep
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Low ✗ Manual fix Fix: Medium Context: File Tests: Update
CWE-916 CWE-760


✓ schema.org compliant