Password Peppering
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
References
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
15 Mar 2026
Edited
22 Mar 2026
Views
20
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
Amazonbot 1
Amazonbot 10
Unknown AI 3
Google 2
Perplexity 2
ChatGPT 2
Ahrefs 1
Also referenced
How they use it
crawler 18
crawler_json 1
pre-tracking 1
Related categories
⚡
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