Cyber Security • Identity and Access Management

Password Storage — Explain password storage concepts (hashing + salting) at a high level

Why modern systems never store your password and how cryptographic hashing and salting protect credentials at rest.

A protected digital shield with binary code and a secure lock for cyber security
Inside this lesson 10 sections 5 questions 8 figures

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

The Compatible Time-Sharing System introduced password-based login for multiple users, storing passwords in a plaintext file. A 1966 software bug accidentally printed the password file as the message of the day, exposing every credential on the system.

1976 • Unix crypt(3)

Robert Morris Sr. and Ken Thompson replaced plaintext storage on Unix with a one-way hash function based on a modified DES cipher. The system stored only the hash; the original password could not be recovered from it.

1976 • Introduction of Salts

The same Unix team added a 12-bit random salt to each password before hashing, multiplying the attacker's work by a factor of 4096 and defeating precomputed lookup tables.

1999 • bcrypt Published

Niels Provos and David Mazières introduced bcrypt, which incorporated an adjustable cost factor so that hashing could be slowed down as hardware grew faster — a concept known as adaptive key stretching.

2015 • Argon2 Wins PHC

The Password Hashing Competition selected Argon2 as the recommended standard. Argon2 resists GPU and ASIC attacks by requiring tunable amounts of both CPU time and memory, setting the current state of the art.

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.

1

One-Way Transformation

A cryptographic hash function maps an input of arbitrary length to a fixed-size digest such that computing the hash is efficient, but inverting it (recovering the input from the digest) is computationally infeasible. This is preimage resistance.
2

Avalanche Effect

Changing even a single bit of the input should flip approximately half the bits of the output. This avalanche effect ensures that similar passwords produce wildly different hashes, preventing attackers from inferring password similarity from hash similarity.
3

Salting

A salt is a unique, randomly generated value concatenated with the password before hashing. It ensures that two users with the same password produce different hashes, defeating rainbow tables and cross-account comparison.
4

Computational Cost

General-purpose hashes like SHA-256 are designed to be fast. Password hashing functions like bcrypt, scrypt, and Argon2 intentionally introduce a tunable work factor (iterations, memory, parallelism) to slow down brute-force attacks without noticeably degrading the user experience.
5

Determinism

Given the same input and the same salt, the hash function must always produce the same output. Without determinism, the server could never verify a returning user's password against the stored hash.
✦

KEY TAKEAWAY

Think of hashing like feeding a document through a cross-cut shredder: the output (confetti) is uniquely determined by the input (document), and you can verify a match by shredding a candidate document and comparing the piles, but reconstructing the original from the confetti is practically impossible. A salt is like spraying a unique dye on each document first — even identical documents produce distinctly colored confetti, so precomputed 'confetti catalogues' are useless.

SECTION 3

Visual Explanation — The Hashing & Salting Pipeline

Password Storage Pipeline: Registration FlowUser Input"P@ssw0rd"Random Saltx7k2mQ9pConcatenatesalt ‖ passwordHash Function(bcrypt / Argon2)cost factor = 12Stored Hash$2b$12$x7k2...a3Fq8ZkLm...Password Verification Pipeline: Login FlowLogin Attempt"P@ssw0rd"Retrieve Saltx7k2mQ9pRe-hashComparenew hash == stored?✓ MATCHGrant access
The top row shows the registration flow: the plaintext password is concatenated with a random salt, then passed through a slow hash function; only the resulting digest (and the salt) are stored. The bottom row shows the login flow: the server retrieves the stored salt, re-hashes the login attempt with the same parameters, and performs a constant-time comparison.

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

H:0,1∗→0,1nH : {0, 1}* → {0, 1}ⁿ

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

Givenh,findingmsuchthatH(m)=hrequiresO(2n)workGiven h, finding m such that H(m) = h requires O(2ⁿ) work

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

digest=H(salt‖password,cost)digest = H(salt ‖ password, cost)

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

Work=∣S∣×∣P∣×C(cost)Work = |S| × |P| × C(cost)

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.

Password Hashing Algorithm ComparisonALGORITHMCPU COSTMEMORY COSTGPU/ASIC RESISTANCEMD5NegligibleNoneNoneSHA-256LowNoneLowbcryptHigh (tunable)4 KB (fixed)ModeratescryptHigh (tunable)High (tunable)HighArgon2Very High (tunable)Very High (tunable)Very HighUNSAFE for passwordsRECOMMENDEDMD5SHAbcryptscryptArgon2
This chart compares five common algorithms across three dimensions: CPU cost, memory cost, and GPU/ASIC resistance. General-purpose hashes (MD5, SHA-256) cluster at the unsafe end because they lack tunable cost and memory hardness. Modern password hashing functions (bcrypt, scrypt, Argon2) offer adjustable resistance that scales with attacker hardware improvements.

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. 1

    Step 1 — User Submits Password

    The user registers with the password C0mput3r$ci. The server receives this over a TLS-encrypted channel and holds it in memory.
  2. 2

    Step 2 — Generate a Random Salt

    The server generates a 128-bit cryptographically secure random salt using a CSPRNG (e.g., /dev/urandom on Linux or os.urandom() in Python). Suppose the base-64 encoded salt is WpKe3IAjF8QYlz2UOkDayO.
    salt = WpKe3IAjF8QYlz2UOkDayO
  3. 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. 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. 5

    Step 5 — Store the Hash String

    The full bcrypt string $2b$12$WpKe3IAjF8QYlz2UOkDayOeFH0RqKXnP6Gq5F0vL7z3dGbKhX1W2u encodes 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. 6

    Step 6 — Login Verification

    When the user later logs in with C0mput3r$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?

A naïve string comparison function returns 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.

PracticeStrengthsLimitations / 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 passwordsPasswords 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

Hashing and salting protect credentials at rest — they are the seatbelt in the security car. But seatbelts do not prevent collisions: password complexity policies, multi-factor authentication, breach monitoring, and rate limiting form the rest of the safety system. A well-hashed database that was breached still means users who reuse passwords elsewhere are at risk, underscoring that password storage is one layer of a multi-layered identity strategy.

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

Explain why storing passwords using reversible encryption (e.g., AES-256-CBC) is considered less secure than storing them as salted hashes, even if the encryption key is kept in a hardware security module (HSM).
Show answer
Reversible encryption means that any entity with access to the decryption key — whether an insider, a compromised process, or an attacker who extracts the key from the HSM — can recover every password in the database in a single operation. This violates the principle of least privilege: the authentication system only needs to verify passwords, not recover them. A one-way hash removes the mathematical possibility of bulk recovery. Even if the database and the application server are fully compromised, the attacker must perform an independent brute-force search for each account, bounded by the hash function's cost factor and the password's entropy.

Question 2: BASIC CALCULATION

A system uses bcrypt with a cost factor of 14. Each hash evaluation takes approximately 1 second. An attacker obtains the database and attempts to crack a single account by brute-forcing all 6-character lowercase alphabetic passwords (a–z). How many candidate passwords exist, and approximately how long would the brute-force attack take?
Show answer
The password space is 266 = 308,915,776 candidates. At 1 second per hash, an exhaustive search takes approximately 308,915,776 seconds ≈ 9.8 years on a single core. Even with 100 cores running in parallel, this reduces to about 36 days. The key insight is that the tunable cost factor transforms what would be a sub-second GPU task with SHA-256 into a multi-year ordeal per account with bcrypt.

Question 3: INTERMEDIATE

A developer argues that using a single, application-wide secret value (a 'pepper') concatenated with each password before hashing with SHA-256 is equivalent to using bcrypt with per-user salts. Identify at least two critical flaws in this reasoning.
Show answer
First, a pepper without a per-user salt means that two users with the same password still produce identical hashes, allowing the attacker to identify common passwords by frequency analysis and to crack multiple accounts simultaneously once any one is found. Second, SHA-256 is not designed to be slow; even with a pepper, a GPU can compute billions of SHA-256 hashes per second, whereas bcrypt's adjustable cost factor deliberately throttles throughput to a few hashes per second per core. Third, if the pepper is ever compromised (through code leak, backup exposure, or insider threat), it provides no further protection — whereas bcrypt's per-user salt remains effective even when the full database is exposed. The pepper adds a defence layer but cannot substitute for the combination of per-user salts and adaptive cost.

Question 4: APPLIED

You are designing a password storage system for a web application expecting 10,000 concurrent login requests per second at peak load. You want to use Argon2id with parameters that take 250 ms per hash on your server hardware. Calculate the number of CPU-seconds required per second at peak, and propose an architectural strategy to handle this without degrading user experience.
Show answer
At 250 ms per hash and 10,000 concurrent logins per second, the system requires 10,000 × 0.25 = 2,500 CPU-seconds per second of compute dedicated to hashing. This means at minimum 2,500 cores must be available simultaneously for hash computation alone. Practical strategies include: (1) a dedicated hashing tier — a horizontally scaled pool of worker nodes responsible solely for Argon2 computation, receiving requests from stateless application servers via a message queue; (2) client-side pre-hashing (e.g., using WebCrypto to perform a fast preliminary hash), sending the intermediate value to the server which then applies the expensive Argon2 step, reducing network-exposed plaintext time; (3) rate limiting and CAPTCHA to ensure 10,000 RPS represents genuine users, not a credential-stuffing attack inflating the load. The key trade-off is that increasing security cost per hash directly multiplies infrastructure cost, and this must be budgeted alongside application performance.

Question 5: CRITICAL THINKING

Quantum computers running Grover's algorithm can search an unstructured space of N elements in O(√N) time, effectively halving the bit-security of hash functions. Discuss how this affects password storage: does Grover's algorithm make salted Argon2 obsolete? What additional defences, if any, should a forward-looking security architect consider?
Show answer
Grover's algorithm reduces the brute-force cost from O(2n) to O(2n/2) for an n-bit hash, halving the effective security level. For a 256-bit hash, this means 128-bit security — still computationally infeasible. However, the real threat to passwords is not hash inversion but brute-force over the password space, which is typically only 20–50 bits of entropy. Grover's algorithm would square-root this space, making a 40-bit entropy password searchable in 220 quantum operations — potentially feasible for a sufficiently powerful quantum computer. The critical mitigating factor is that Grover's algorithm provides only a quadratic speedup and cannot be parallelised efficiently (unlike classical GPUs); each additional qubit is enormously costly. Defences include: doubling the cost factor to restore the pre-quantum margin, migrating to longer passphrases that increase password entropy, and transitioning to passwordless authentication (FIDO2/passkeys) that removes shared secrets entirely. Salted Argon2 is not 'obsolete' in the quantum era — but the margin of safety shrinks, reinforcing the argument for eliminating passwords where possible.

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.

Varsity Tutors • Cyber Security • Password Storage — Explain password storage concepts (hashing + salting) at a high level