Daily Breach

Cyber Weekly

The Definitive Guide to Modern Password Hashing

Modern Password Hashing

Introduction

Despite growing interest in passwordless authentication, password-based login systems remain the backbone of digital identity security. From social platforms to financial services, billions of accounts still rely on passwords as their primary authentication factor.

While most companies have moved away from plaintext password storage, many still rely on outdated or improperly configured hashing mechanisms. These implementation flaws are what transform manageable security incidents into massive data exposures, rendering the initial effort to hash almost useless.

This article breaks down how password hashing works, why many implementations fail, and how modern algorithms like Argon2 and bcrypt should be used correctly, supported by dummy JavaScript examples you can safely take inspiration from and use in your production systems accordingly.

Why Password Hashing Matters

Storing passwords in plaintext or via reversible encryption is a critical security risk. Because encryption is designed to be undone with a key, a stolen key means stolen passwords. Hashing, however, is a one-way process, making it the only secure way to store credentials

Cryptographic hashing transforms a password into a fixed-length output that cannot be reversed to retrieve the original value. Even if attackers steal the database, properly hashed passwords remain protected.

However, not all hash functions are suitable for password storage.

The Problem With Legacy Hashing Algorithms

Early systems relied heavily on fast cryptographic hash functions such as:

These algorithms were designed for data integrity, not authentication security. Their speed is their biggest weakness.

Modern GPUs and ASICs can compute billions of these hashes per second, making brute-force and dictionary attacks trivial when passwords are weak or reused.

Even SHA-256, while cryptographically strong, is unsuitable for password hashing because attackers benefit from its performance optimizations.

What Makes a Password Hash Secure?

Modern password hashing algorithms intentionally work against attackers by being:

  • Slow by design
  • Memory-intensive
  • Resistant to parallel processing

This shifts the economic advantage away from attackers and makes large-scale cracking impractical.

Modern Password Hashing Algorithms Compared

PBKDF2

PBKDF2 introduced the concept of key stretching, running the hash function thousands of times to slow down brute-force attempts.

  • Widely supported
  • Still acceptable in constrained environments
  • Requires very high iteration counts to remain secure today

PBKDF2 (Password-Based Key Derivation Function 2) works by converting a user’s password into a secure, stored hash using salt, iterations, and repeated cryptographic operations.

1. Password and Salt as Inputs

The process begins with two inputs:

  • The password provided by the user (e.g., strongPassword@715)
  • A randomly generated salt (e.g., a3f90c4...)

The salt ensures that even if two users choose the same password, their final hashes will be different. This prevents precomputed attacks such as rainbow tables.

2. Iteration Count Defines the Work Factor

An iteration count (e.g., 100,000) is supplied to PBKDF2.
This value controls how many times the hashing operation is repeated, deliberately slowing down the process.

While this delay is barely noticeable during normal logins, it significantly increases the cost of brute-force attacks.

3. First Hash (U₁)

Inside PBKDF2, a cryptographic function (typically HMAC-SHA256) is used.

The first intermediate value is computed as:

U₁ = HMAC(password, salt || block_index)

This produces the first derived output, labeled U₁ in the diagram.

4. Repeated Hashing (U₂ … Uc)

PBKDF2 then repeatedly applies the same HMAC function:

U₂ = HMAC(password, U₁)
U₃ = HMAC(password, U₂)
...
Uc = HMAC(password, Uc−1)

Each iteration depends on the output of the previous one, forcing the computation to be sequential and preventing shortcuts.

5. XOR Combination of All Iterations

Once all iterations are completed, PBKDF2 combines every intermediate value using a bitwise XOR operation:

Final Hash = U₁ ⊕ U₂ ⊕ ... ⊕ Uc

This ensures that every iteration contributes to the final result. Skipping even one step would produce a completely different hash.

Bcrypt

bcrypt was a major leap forward, introducing:

  • Adjustable cost factor
  • Automatic salting
  • Resistance to rainbow table attacks

bcrypt remains widely used and trusted, though its memory usage is limited compared to newer designs.

bcrypt achieves password security by repeatedly rebuilding the internal state of the Blowfish cipher before performing a final encryption step.


1. Password and Cost Factor as Inputs

The process starts with two inputs:

  • The password provided by the user
  • A cost factor (e.g., 12), which determines how slow bcrypt should be

The cost factor does not represent time directly. Instead, it defines how many times Expensive Key Schedule Blowfish (Eksblowfish) which is an expensive internal operation is repeated:

cost = 12 → 2¹² = 4096 rounds

Increasing the cost by 1 doubles the amount of work bcrypt performs.

2. What Is Blowfish?

Blowfish is a symmetric block cipher designed by Bruce Schneier. While it is older than modern ciphers like AES, it has a unique property that makes it ideal for password hashing:

Blowfish has a very expensive key setup phase.

Blowfish internally uses two main data structures:

  • A P-array
    This is a table of internal subkeys that control how the encryption steps are performed.
  • Four large S-boxes
    These are large lookup tables used to substitute values during encryption, adding confusion and complexity.

Which simply means:

Before Blowfish can encrypt anything, it must build and initialize large internal tables in memory that completely depend on the provided key.

Setting up these tables requires:

  • Heavy computation
  • Large amounts of memory access
  • Multiple internal encryption steps

This setup process is intentionally slow.

bcrypt does not use Blowfish to encrypt user data. Instead, it deliberately exploits this expensive key setup phase to make each password guess slow and costly for attackers.

3. Expensive Key Schedule Blowfish (EksBlowfish)

The core of bcrypt is the Expensive Key Schedule Blowfish (EksBlowfish), shown in the diagram as the repeated block.

bcrypt first generates a random salt internally, then enters the key-setup phase.

Inside EksBlowfish, bcrypt performs the following loop:

Repeat 2^cost times:
  ExpandKey(password)
  ExpandKey(salt)

What ExpandKey Does

ExpandKey does not hash or encrypt data. Instead, it:

  • Rewrites Blowfish’s internal P-array and S-boxes
  • Mixes the input (password or salt) into the cipher state
  • Forces large, state-dependent memory operations

By alternating between the password (secret) and the salt (random, public), bcrypt ensures:

  • Identical passwords produce different results
  • No intermediate state can be reused
  • Precomputation and rainbow tables are infeasible

This repeated rebuilding of the cipher state is where most of bcrypt’s time is spent

4. Blowfish Encryption

After the key schedule phase completes, bcrypt performs the actual Blowfish encryption.

At this stage:

  • A fixed internal string is encrypted
  • The encryption is repeated a fixed number of times
  • The output becomes the final bcrypt hash

This encryption step happens once, not 4096 times. The security comes from the expensive preparation of the cipher, not from repeated encryption.

7. Final Hashed Password

The final output is a bcrypt hash string (e.g., $2a$12$...) that contains:

  • The bcrypt version
  • The cost factor
  • The salt
  • The derived hash

This single string is stored in the database and contains everything needed for password verification.

Scrypt

scrypt was designed to defeat GPU and ASIC attacks by forcing high memory usage.

  • Configurable CPU and memory cost
  • Strong resistance to hardware acceleration
  • More complex to tune correctly

Scrypt forces attackers to use large amounts of memory per password guess, making GPU and ASIC attacks significantly more expensive.

1. Password and Salt → PBKDF2 (Initial Setup)

The process begins with two inputs:

  • The password provided by the user
  • A randomly generated salt

These are first fed into PBKDF2 (using HMAC-SHA256).

PBKDF2 stretches the password and salt into initial key material using repeated HMAC operations.

In scrypt, this first PBKDF2 step is not the main security mechanism. Its role is to:

  • Mix the password and salt together securely
  • Produce initial blocks of data that will later be processed by the memory-hard function

2. Enter the Memory-Hard Function

After the initial PBKDF2 step, scrypt enters its defining component: the memory-hard mixing phase. This phase is responsible for most of scrypt’s security.

This part of the diagram starts with SMix.

3. SMix: Handling Parallelization (p)

SMix which means Sequential Mix is the top-level mixing function in scrypt. Its primary responsibility is to handle the parallelization factor, denoted by p.

What does p control?

  • p determines how many independent memory-hard instances are run
  • Each instance runs its own copy of the memory-hard algorithm

For example:

  • p = 1 → one memory-hard computation
  • p = 4 → four independent computations running in parallel

Why is p = 1 considered ideal?

For most password-hashing use cases:

  • p = 1 already provides strong memory hardness
  • Increasing p multiplies memory and CPU usage linearly
  • Higher p values increase server load and DoS risk

In practice:

p = 1 is the recommended and most commonly used value.

SMix takes the output of PBKDF2, splits it into p segments, and passes each segment independently into ROMix.

4. ROMix : The Memory-Hard Core (N)

Each SMix “lane” now enters ROMix or Read Only Mix, which is the core memory-hard algorithm in scrypt.

What ROMix does (conceptually)

ROMix enforces memory hardness by:

  1. Allocating a large array of memory blocks
  2. Filling that memory with data derived from the password
  3. Repeatedly accessing this memory in a data-dependent and unpredictable way

This prevents attackers from trading memory for speed.

How N is used in ROMix

N controls how many memory blocks are allocated.

  • N is a count, not a size
  • It must be a power of two

In the diagram:

N = 2¹⁴ = 16384

This means:

  • ROMix stores 16,384 blocks in memory
  • Each block is revisited many times during computation

16, 384 number of blocks is a widely accepted baseline

With common parameters, this results in roughly 16 MB of memory per password hash, which is:

  • Acceptable for servers
  • Extremely costly for large-scale attackers

5. BlockMix : Processing Memory Blocks (r)

Inside ROMix, memory blocks are processed using a function called BlockMix.

Why BlockMix exists

ROMix needs a way to:

  • Transform blocks
  • Mix old and new data together
  • Produce unpredictable memory access patterns

BlockMix performs this transformation on blocks of data before they are stored or reused.

How r is used in BlockMix

The parameter r controls the size of each memory block.

  • Block size = 128 × r bytes

In the diagram:

r = 8

So each block is:

128 × 8 = 1024 bytes (1 KB)

The total memory usage is therefore:

128 × r × N

For:

N = 16384, r = 8
→ ~16 MB

BlockMix ensures that increasing r increases:

  • Memory footprint
  • Amount of data processed per step

6. Salsa20/8 : The Mixing Primitive

Inside BlockMix, scrypt uses Salsa20/8.

What Salsa20/8 is (and is not)

  • Salsa20 is a stream cipher
  • scrypt uses only its core mixing function
  • /8 means 8 rounds (reduced from the original 20)

Important:

Salsa20/8 is not used for encryption here.

Its role is to:

  • Rapidly and securely mix data
  • Destroy patterns
  • Ensure strong diffusion between memory blocks

Salsa20/8 is fast, simple, and well-studied mainly ideal for repeated mixing inside a memory-hard loop.

7. Why This Combination Is Effective

The interaction between:

  • SMix (parallelization)
  • ROMix (memory allocation and access)
  • BlockMix (block processing)
  • Salsa20/8 (fast cryptographic mixing)

forces every password guess to:

  • Allocate large amounts of memory
  • Access that memory unpredictably
  • Perform sequential operations that cannot be parallelized cheaply

This is what makes scrypt memory-hard.

8. Final PBKDF2: Output Derivation

After the memory-hard phase completes, scrypt runs PBKDF2 one final time.

Why PBKDF2 is used again

The final PBKDF2 step:

  • Compresses the mixed memory output
  • Produces a fixed-length derived key (e.g., 64 bytes)
  • Ensures compatibility with standard key formats

Basically this step does not add memory hardness, it finalizes the result into a usable hash.

Argon2 (Recommended)

Argon2 is the current gold standard in password hashing.

  • Winner of the Password Hashing Competition
  • Memory-hard and configurable
  • Available in three variants

Argon2id is recommended for most applications because it balances security against side-channel and GPU-based attacks.

Correctly configured Argon2 can reduce real-world password compromise rates by over 40 percent compared to traditional hashing methods.

The diagram illustrates how Argon2id (the recommended variant) processes a password by combining cryptographic hashing with deliberate memory usage.

1. Core Parameters

These parameters define how Argon2 operates, and they are treated as cryptographic inputs rather than simple configuration values.

  • Memory size (m)
    Specifies how much memory Argon2 allocates, measured in kilobytes (KB).
    Example: m = 65536 KB (64 MB)
  • Time cost (t)
    Specifies how many times Argon2 iterates over the allocated memory.
  • Parallelism (p)
    Specifies how many independent memory lanes are processed in parallel.
  • Hash length
    Specifies the length of the final output (typically 32 or 64 bytes).

2. Argon2 Variants

Argon2 defines three variants, which differ in how memory is accessed:

  • Argon2d
    Uses data-dependent memory access. This offers strong resistance against GPU and ASIC attacks but is vulnerable to side-channel attacks. Not recommended for password hashing.
  • Argon2i
    Uses data-independent memory access. This is safer against side-channel attacks but slightly weaker against GPU optimizations.
  • Argon2id (recommended)
    A hybrid approach:
    • Early passes use Argon2i-style access (side-channel safe)
    • Later passes use Argon2d-style access (GPU resistant)

Argon2id is the recommended variant for password hashing because it balances security and practicality.

3. BLAKE2b Initialization

After collecting the password, salt, and parameters, Argon2 performs an initial hashing step using BLAKE2b.

What is BLAKE2b?

BLAKE2b is a modern cryptographic hash function designed to be:

  • Fast
  • Secure
  • Simple to implement
  • Suitable as a cryptographic building block

Argon2 uses BLAKE2b in place of older hashes like SHA-256 because it provides better performance and a clean internal structure.

Why BLAKE2b Is Used Here

Argon2 hashes together:

  • The password
  • The salt
  • All parameters (memory size, time cost, parallelism, variant, version)

This produces an initial digest that:

  • Cryptographically binds the parameters to the hash
  • Ensures that changing any parameter results in a completely different output
  • Seeds the memory-hard computation that follows

At this stage, no large memory is used yet as BLAKE2b only establishes the starting state.

4. Memory Allocation and the Memory Table

After initialization, Argon2 allocates a large block of memory based on the m parameter.

What Is the Memory Table?

The memory table is:

  • A large array of fixed-size blocks
  • Each block is exactly 1 KB
  • Total size equals m KB

Example:

m = 65536 KB → 65,536 blocks of 1 KB each

This table exists entirely in RAM and must remain allocated for the entire duration of the hash computation.

Why This Memory Table Exists

The memory table is the core of Argon2’s security:

  • It forces each password guess to reserve large amounts of memory
  • Memory is expensive and difficult to parallelize efficiently
  • Attackers cannot replace memory usage with faster computation

5. Filling the Memory Blocks

Once the memory table is allocated, Argon2 begins filling the blocks.

Each block is computed using:

  • The previous block
  • A reference block elsewhere in memory
  • A compression function derived from BLAKE2b

This process:

  • Writes data into every memory block
  • Creates dependencies between blocks
  • Prevents on-the-fly recomputation

After this phase, the entire memory table is populated for the first time.

6. Repeat Passes (t)

After the initial fill, Argon2 does not allocate new memory.

Instead, it performs additional passes over the same memory table, controlled by the time cost parameter t.

For example:

t = 3

Means:

  • The same memory table is reused
  • All blocks are read and modified three times
  • Each pass depends on the results of the previous one

This increases computational cost without increasing memory usage and enforces strict sequential execution.

7. Final BLAKE2b and Output Generation

After all memory passes are complete, Argon2 performs a final BLAKE2b hash.

Why BLAKE2b Is Used Again

The final BLAKE2b step:

  • Compresses the large memory state into a fixed-length output
  • Produces the final hash value
  • Ensures uniform and secure output formatting

This final hash is then encoded together with:

  • The variant
  • The parameters
  • The salt

Resulting in a self-contained string suitable for storage and verification.

Why Argon2 Is Recommended

Argon2 is recommended over older password hashing algorithms because it:

  • Is memory-hard by design
  • Allows explicit control over memory, time, and parallelism
  • Resists GPU and ASIC attacks effectively
  • Avoids the complexity and tuning pitfalls of earlier schemes
  • Was selected as the winner of the Password Hashing Competition

Among its variants, Argon2id provides the best balance of security and practical deployment.

JavaScript Password Hashing Examples

1. Password Hashing with Argon2 (Recommended)

import argon2 from "argon2";

export async function hashPassword(password) {
  const hash = await argon2.hash(password, {
    type: argon2.argon2id, // ✅ recommended variant
    memoryCost: 65536,     // 64 MB (in KB)
    timeCost: 3,           // number of passes
    parallelism: 1,        // lanes
    hashLength: 64         // output length in bytes
  });

  return hash;
}

export async function verifyPassword(password, storedHash) {
  return await argon2.verify(storedHash, password);
}

Why this works

  • Uses Argon2id
  • Memory-hard configuration
  • Automatically handles salting

2. Password Hashing with bcrypt (Alternative)

npm install bcrypt
import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;

export async function hashPassword(password) {
  return await bcrypt.hash(password, SALT_ROUNDS);
}

export async function verifyPassword(password, hash) {
  return await bcrypt.compare(password, hash);
}

When to use bcrypt

  • Legacy system compatibility
  • Environments with limited memory

Attack Reality Check

No hashing algorithm can fully protect weak passwords.

Studies consistently show that:

  • Common passwords are cracked in over 95 percent of cases
  • Dictionary attacks remain extremely effective
  • Hashing must be paired with strong password policies

Hashing protects stored credentials, not poor user choices.

Key Takeaways

  • Password hashing is non-negotiable
  • Fast hash functions are unsafe for authentication
  • Argon2id is the preferred modern choice
  • Correct configuration matters as much as algorithm selection
  • Weak passwords undermine all cryptographic defenses

Final Thoughts

Secure password storage is not a single technical decision. It is a system-wide security discipline involving cryptography, configuration, developer education, and ongoing maintenance.

Organizations that treat password hashing as a checkbox will continue to suffer breaches. Those that treat it as a core security primitive significantly reduce their risk exposure in an increasingly hostile threat landscape.

References / Sources

  1. OWASP Foundation
    Password Storage Cheat Sheet
    Authoritative best-practice guidance on password hashing algorithms, salting, and configuration.
  2. Argon2 Password Hashing Competition
    The Password Hashing Competition (PHC)
    Official documentation and design rationale for Argon2.
    https://password-hashing.net/
  3. RFC 9106
    Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
    IETF standard defining Argon2 variants and parameters.
    https://www.rfc-editor.org/rfc/rfc9106.html
  4. Naiakshina et al. (2019)
    “If You Want, I Can Store the Encrypted Password”
    Empirical study on real-world password storage mistakes by developers.
    https://dl.acm.org/doi/10.1145/3290605.3300370
  5. Tippe & Berner (2025)
    Evaluating Argon2 Adoption and Effectiveness in Real-World Software
    Analysis of Argon2 deployments and parameter misconfigurations.
    https://arxiv.org/abs/2504.17121
  6. Provos & Mazieres (1999)
    A Future-Adaptable Password Scheme
    Foundational paper introducing bcrypt.
    https://www.usenix.org/legacy/event/usenix99/provos/provos.pdf
  7. Colin Percival (2009)
    Stronger Key Derivation via Sequential Memory-Hard Functions
    Original scrypt paper.
    https://www.tarsnap.com/scrypt/scrypt.pdf
  8. Stallings, William
    Cryptography and Network Security: Principles and Practice
    Widely used reference for cryptographic hash functions and authentication concepts.
  9. Ars Technica
    25-GPU Cluster Cracks Every Standard Windows Password in Under 6 Hours
    Real-world demonstration of GPU-based password cracking.
    https://arstechnica.com/information-technology/2012/12/25-gpu-cluster-cracks-every-standard-windows-password-in-6-hours/
  10. Specops Software Research
    How Long Does It Take to Crack Hashed Passwords?
    Practical cracking benchmarks for MD5, SHA-1, and SHA-256.
    https://specopssoft.com/blog/
Shubhendu Sen

Shubhendu Sen

About Author

Shubhendu Sen is a law graduate and former software developer with two years of professional experience, having worked on both frontend and backend development of web applications, primarily within the JavaScript ecosystem. He is currently pursuing a Master of Cyber Law and Information Security at NLIU Bhopal and is ISC2 Certified in Cybersecurity (CC). His interests include cyber law, malware research, security updates, and the practical implementation and audit of GRC frameworks.

Leave a Reply

Your email address will not be published. Required fields are marked *