SECTION 1
Historical Context & Motivation
The earliest multi-user computer systems of the 1960s faced a deceptively simple problem: how does the machine verify that the person sitting at a terminal is who they claim to be? The first solution was equally simple — store every user's plaintext password in a file on disk and compare it character-by-character at login. This approach worked until attackers (or curious insiders) gained read access to the file, instantly compromising every account on the system. The history of password storage is, in essence, a series of increasingly sophisticated responses to the fundamental insight that storing secrets in recoverable form is inherently dangerous.
1961 • CTSS at MIT
1976 • Unix crypt(3)
1976 • Introduction of Salts
1999 • bcrypt Published
2015 • Argon2 Wins PHC
This timeline reveals a recurring pattern: each generation of password storage was broken by advances in attacker capability — faster CPUs, GPUs, rainbow tables, or commodity cloud computing — and the security community responded with stronger primitives. The central question this lesson addresses is: how can a system verify a password without ever storing it, and what makes modern hashing and salting schemes resistant to both brute-force and precomputation attacks?
SECTION 2
Core Principles & Definitions
Secure password storage rests on a small set of interlocking principles, each of which addresses a distinct threat. Understanding these principles individually is essential before seeing how they compose into a complete defense.
One-Way Transformation
Avalanche Effect
Salting
Computational Cost
Determinism
KEY TAKEAWAY
SECTION 3
Visual Explanation — The Hashing & Salting Pipeline
Several details in the diagram deserve emphasis. First, the plaintext password never persists beyond the duration of the hashing operation — it exists in volatile memory only long enough to compute the digest. Second, the salt is stored in the clear alongside the hash (often encoded into the same string, as in bcrypt's $2b$12$... format). This is not a vulnerability: the salt's purpose is not to be secret but to ensure uniqueness. Third, the cost factor parameterises the hash function's internal loop count, allowing administrators to tune the computation time per hash — typically targeting 100–500 ms per verification on current hardware.
SECTION 4
How It Works — The Mathematics of Hashing & Salting
Although this lesson treats password storage at a high level, a precise understanding of the relevant mathematical properties is essential for reasoning about security guarantees. Cryptographic hash functions are built from compression functions composed iteratively (Merkle–Damgård construction) or from sponge functions (as in SHA-3), but their security rests on three core properties expressed below.
HASH FUNCTION DEFINITION
A hash function H maps an arbitrary-length binary string to a fixed-length n-bit digest. For SHA-256, n = 256; for bcrypt's internal Blowfish state, n = 192.
PREIMAGE RESISTANCE
An attacker who possesses only the hash digest h must try on the order of 2n candidate inputs to find one that hashes to h. For n = 256 this is computationally infeasible.
SALTED HASH COMPUTATION
The operator ‖ denotes concatenation. The salt is a random value (typically 128 bits). The cost parameter controls the number of internal iterations. In bcrypt, cost = 12 means 212 = 4,096 rounds of the Blowfish key schedule.
BRUTE-FORCE SEARCH SPACE WITH SALT
Without salts, an attacker precomputes hashes for the password space |P| once and reuses them against all accounts. With a unique salt per account, the attacker must redo the computation for each of the |S| distinct salts. C(cost) represents the time for one hash evaluation, which grows exponentially with the cost parameter.
The interplay of these equations reveals the defense-in-depth strategy. Preimage resistance prevents inversion of a single hash. Salting multiplies the attacker's workload across multiple accounts. The adjustable cost factor ensures that even as hardware improves, the time per hash attempt can be increased to maintain a constant security margin. Together, they transform password cracking from a feasible table lookup into an economically prohibitive brute-force search.
SECTION 5
Modern Password Hashing Algorithms
Not all hash functions are appropriate for password storage. General-purpose functions like MD5 and SHA-256 are designed for speed, which is the opposite of what defenders need when an attacker is trying billions of guesses per second. The following diagram and table compare the major password-specific hashing algorithms and their resistance properties.
The distinction between memory-hard and purely CPU-bound algorithms is crucial to understanding modern threat models. GPUs excel at parallel, compute-intensive tasks — an NVIDIA RTX 4090 can compute roughly 164 billion MD5 hashes per second. However, GPU cores have limited local memory, so algorithms like scrypt and Argon2 that require megabytes of RAM per hash evaluation cannot be efficiently parallelised across thousands of GPU cores. The Argon2id variant, specifically, combines data-independent memory access patterns (resistance to side-channel attacks) in a first pass with data-dependent patterns (resistance to time-memory tradeoff attacks) in subsequent passes, making it the current OWASP recommendation for password storage.
SECTION 6
Worked Example — Storing and Verifying a Password with bcrypt
Let us walk through a complete registration and login cycle using bcrypt. This example illustrates the concrete data transformations at each stage and shows why an attacker with full database access still cannot recover the original password.
Registering and Authenticating a User with bcrypt
- 1
Step 1 — User Submits Password
The user registers with the passwordC0mput3r$ci. The server receives this over a TLS-encrypted channel and holds it in memory. - 2
Step 2 — Generate a Random Salt
The server generates a 128-bit cryptographically secure random salt using a CSPRNG (e.g.,/dev/urandomon Linux oros.urandom()in Python). Suppose the base-64 encoded salt isWpKe3IAjF8QYlz2UOkDayO.salt =WpKe3IAjF8QYlz2UOkDayO - 3
Step 3 — Choose a Cost Factor
The administrator has configured a cost factor of 12, meaning 212 = 4,096 iterations of the Blowfish key schedule. On current server hardware, this yields approximately 250 ms per hash — fast enough that legitimate users barely notice, but slow enough to cripple offline brute-force attacks.cost = 12 → 4,096 iterations ≈ 250 ms/hash - 4
Step 4 — Compute the Hash
The server passes the password, salt, and cost to the bcrypt function. Internally, bcrypt initialises the Blowfish cipher with the password as the key, then repeatedly encrypts a constant string ("OrpheanBeholderScryDoubt") through 4,096 rounds of key expansion incorporating both the password and the salt. The output is a 192-bit hash.hash =$2b$12$WpKe3IAjF8QYlz2UOkDayOeFH0RqKXnP6Gq5F0vL7z3dGbKhX1W2u - 5
Step 5 — Store the Hash String
The full bcrypt string$2b$12$WpKe3IAjF8QYlz2UOkDayOeFH0RqKXnP6Gq5F0vL7z3dGbKhX1W2uencodes the algorithm identifier ($2b$), the cost (12), the salt (first 22 characters after the cost), and the hash (remaining characters). This single string is stored in the user database. The plaintext password is zeroed from memory.Database stores:$2b$12$WpKe3IAjF8QYlz2UOkDayO...— the plaintext password is discarded. - 6
Step 6 — Login Verification
When the user later logs in withC0mput3r$ci, the server retrieves the stored hash string, extracts the algorithm, cost, and salt, then recomputes the hash with the submitted password. If the resulting digest matches the stored digest in a constant-time comparison (to prevent timing side-channels), access is granted.bcrypt_verify("C0mput3r$ci", stored_hash) → TRUE — access granted
Why Constant-Time Comparison?
false as soon as it encounters the first mismatched byte, meaning wrong guesses that match more prefix bytes take slightly longer. An attacker measuring response times in microseconds can exploit this to guess the hash byte-by-byte — a timing attack. Constant-time comparison functions always inspect every byte, leaking no information about which bytes match.SECTION 7
Strengths, Limitations & Common Pitfalls
No defensive technique exists in isolation; understanding the boundaries of hashing and salting is as important as understanding the mechanisms themselves. The table below contrasts correct practices with common deployment errors and their consequences.
| Practice | Strengths | Limitations / Pitfalls |
|---|---|---|
| Salted, adaptive hash (bcrypt, Argon2) | Defeats rainbow tables; brute-force cost is tunable; each account is independently protected. | Does not protect against phishing, credential reuse, or weak passwords. Cost factor must be periodically re-tuned as hardware improves. |
| Unsalted fast hash (MD5, SHA-1) | Trivial to implement; extremely fast verification. | Vulnerable to rainbow tables and GPU brute-force. A single leaked database compromises all identical passwords across accounts. |
| Encryption (AES) of passwords | Passwords can be recovered if necessary (e.g., for migration). | If the encryption key is compromised, all passwords are exposed instantly. Violates the principle that verification should not require the ability to recover the secret. |
| Pepper (server-side secret key) | Adds a layer of defense even if the database is fully compromised; attacker also needs the pepper. | Key management complexity; if the pepper is lost, all stored credentials become unverifiable. Not a replacement for salting. |
Comparison of password storage approaches and their security trade-offs.
KEY TAKEAWAY
SECTION 8
Connection to Advanced Theory & Emerging Techniques
Password hashing is only one piece of the broader authentication landscape. As threats evolve, the field is moving toward techniques that reduce or eliminate the need to store password-derived secrets altogether. Understanding how classical hashing relates to these emerging approaches provides important context for where the industry is heading.
| Classical (This Lesson) | Advanced / Emerging |
|---|---|
| Server stores salted hash of password; verifies by re-hashing the candidate. | OPAQUE / aPAKE: Asymmetric password-authenticated key exchange. The server never sees the plaintext password, not even during registration. The protocol uses an oblivious PRF so the server stores a credential file it cannot evaluate without the client. |
| Security relies on the computational cost of hash inversion. | Passkeys (FIDO2/WebAuthn): Eliminates passwords entirely. Authentication uses public-key cryptography; the server stores only the public key. Phishing-resistant by design since credentials are origin-bound. |
| Argon2 is memory-hard to resist GPU/ASIC attacks. | Balloon Hashing: A provably memory-hard function analysed in the random oracle model. Provides formal security guarantees that Argon2's heuristic analysis cannot. |
| Cost factor is manually tuned by administrators. | Adaptive re-hashing on login: When a user authenticates, the system transparently re-hashes with a higher cost factor or upgrades to a newer algorithm (e.g., from bcrypt to Argon2id) without requiring a password reset. |
How classical password hashing connects to advanced and emerging authentication techniques.
The trend is clear: the security community recognises that any scheme requiring users to create and remember high-entropy secrets is fundamentally limited by human behaviour. Passkeys and asymmetric protocols represent a paradigm shift away from shared secrets. Nevertheless, password-based authentication will persist in legacy systems for years, making a solid understanding of hashing and salting an essential part of every security engineer's toolkit.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
Question 2: BASIC CALCULATION
Show answer
Question 3: INTERMEDIATE
Show answer
Question 4: APPLIED
Show answer
Question 5: CRITICAL THINKING
Show answer
SUMMARY
Summary — Password Storage Concepts
Summary
Secure password storage transforms the authentication problem from 'protect a secret' to 'store only an irreversible fingerprint.' A cryptographic hash function provides preimage resistance — the one-way property that prevents recovering the password from the digest. A unique, random salt per account ensures that identical passwords produce different hashes, defeating rainbow tables and cross-account correlation. The avalanche effect prevents similarity-based inference, and an adjustable cost factor keeps brute-force attacks economically infeasible even as hardware improves.
Modern best practice centres on Argon2id (or bcrypt as a well-understood alternative), both of which incorporate built-in salting and tunable CPU/memory costs. Password hashing is a critical defence-in-depth layer but does not replace multi-factor authentication, complexity policies, or the emerging shift toward passwordless authentication (FIDO2/passkeys). Understanding these foundations equips you to evaluate, audit, and design authentication systems that protect user credentials even when everything else has been compromised.