AI Overview SummaryArgon2 is replacing Bcrypt as the gold standard for password hashing. Learn the technical differences, memory hardness mechanics, and optimization trade-offs.
The Landscape of Password Storage
For over two decades, Bcrypt has been the default recommendation for secure password storage in web applications. Originally designed by Niels Provos and David Mazières in 1999 based on the Blowfish cipher, Bcrypt introduced a key concept that revolutionized password hashing: configurable work factors (or rounds). This allowed developers to scale the computational complexity of password hashing alongside hardware improvements, keeping brute-force attacks expensive.
However, as hardware architectures shifted toward massive parallelism—fueled by Graphical Processing Units (GPUs), Field-Programmable Gate Arrays (FPGAs), and Application-Specific Integrated Circuits (ASICs)—Bcrypt's defensive model began to show its limitations. Bcrypt is CPU-hard (time-complex) but relies on extremely small memory structures (4 KB). This makes it cheap to implement in hardware accelerators, allowing attackers to check millions of Bcrypt hashes per second on custom rigs.
In response to these hardware advances, the Password Hashing Competition (PHC) selected Argon2 as the official winner in July 2015. Developed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich, Argon2 introduces memory hardness alongside time complexity and parallelism controls.
In this deep dive, we will analyze the mathematical and architectural differences between Bcrypt and Argon2, dissect how memory hardness works at the hardware layer, compare the three variants of Argon2, and provide clear configuration guidelines for modern production environments.
Technical Dissection: Bcrypt
Bcrypt operates by using an adapted version of the Blowfish key setup process, known as Eksblowfish (Expensive Key Setup Blowfish). Unlike standard Blowfish, where key expansion and data encryption are distinct steps, Eksblowfish interweaves the salt and the password iteratively during the state initialization.
The Eksblowfish Algorithm
Bcrypt's memory footprint is bound to the Blowfish state, specifically the P-array and S-boxes:
- The P-array consists of eighteen 32-bit subkeys ($18 \times 4\text{ bytes} = 72\text{ bytes}$).
- Four S-boxes, each containing 256 entries of 32-bit values ($4 \times 256 \times 4\text{ bytes} = 4,096\text{ bytes}$ or $4\text{ KB}$).
The Eksblowfish setup runs as follows:
EksblowfishSetup(cost, salt, password)
State <- Initialize Blowfish state with P-array and S-boxes
For i = 1 to 2^cost
State <- BlowfishEncrypt(State, password XOR salt)
The fundamental security properties of Bcrypt are:
- Entropy Injection: The salt (typically 128 bits) is mixed continuously into the state to prevent precomputation (rainbow table) attacks.
- Exponential Scaling: The
costparameter determines the number of iterations ($2^{\text{cost}}$). Incrementing the cost factor by $1$ doubles the execution time. - Internal Memory Limits: The lookup space is strictly limited to $4\text{ KB}$.
The GPU Cracking Vulnerability
Bcrypt's main flaw is its reliance on a tiny $4\text{ KB}$ S-box state. In modern computing, $4\text{ KB}$ of memory fits entirely within the L1 cache of a CPU core. More importantly, it fits comfortably within the local memory blocks of individual compute units in a modern GPU (like NVIDIA CUDA cores or AMD Stream processors).
Because memory requirements are so low, a GPU chip containing thousands of small, parallel processing cores can execute thousands of independent instances of the Bcrypt algorithm simultaneously without hitting memory bandwidth bottlenecks. An attacker using a high-end consumer GPU can compute Bcrypt hashes at an alarming scale compared to a standard server CPU, neutralizing the work factor advantage.
Technical Dissection: Argon2
Argon2 is specifically designed to eliminate the hardware acceleration advantage. It accomplishes this by utilizing a flexible, multidimensional parameter system:
- Memory Cost ($m$): The amount of RAM to use, configurable in kibibytes (KiB).
- Time Cost ($t$): The number of passes over the memory array.
- Parallelism ($p$): The number of threads to spawn to calculate segments of the memory matrix concurrently.
Memory-Hard Design
At its core, Argon2 fills a multi-dimensional array of memory blocks (each block is 1,024 bytes) with pseudo-random data. The blocks are computed sequentially within a slice, but each new block's content is derived from a hash of preceding blocks. Because the index of the referenced block is determined dynamically (either pseudo-randomly or data-dependently), the entire memory footprint must reside in active physical RAM (random-access memory).
If an attacker tries to build an ASIC or custom FPGA chip to crack Argon2, they cannot simply build dense, lightweight compute cores on the chip. Each compute core requires dedicated access to megabytes of physical RAM. The cost of manufacturing chips with high memory bandwidth and capacity scales exponentially, effectively closing the performance gap between defender CPUs and attacker hardware.
Argon2 Variants: d, i, and id
Argon2 is available in three distinct profiles, each targeting a specific threat model.
1. Argon2d (Data-Dependent)
Argon2d uses data-dependent memory addressing. The index of the previous memory block used to generate the next block is computed directly from the values stored in the current block.
- Strength: Offers the highest resistance against GPU/ASIC cracking attacks. Because memory addresses are dynamically calculated, it is incredibly difficult to optimize using pre-cached memory schemes.
- Weakness: Vulnerable to side-channel cache-timing attacks. An attacker with local access to the execution CPU can observe cache hits and misses to infer the memory lookup patterns, potentially reconstructing the password.
- Use Case: Cryptocurrencies, backend storage of offline data where side-channels are not a factor.
2. Argon2i (Data-Independent)
Argon2i uses data-independent memory addressing. The memory access patterns are determined beforehand by a generator function that relies solely on the inputs (salt, cost parameters, and pass number) and not the intermediate values of the memory matrix.
- Strength: Invulnerable to side-channel cache-timing attacks. The CPU cache line lookups are completely uniform regardless of the password structure.
- Weakness: Lower resistance against GPU/ASIC attacks. Because the memory access sequences are predictable, attackers can employ time-memory trade-offs (TMTO) to compute the algorithm using significantly less RAM by recalculating blocks on-the-fly.
- Use Case: Identification protocols, client-side browser hashing, and web auth modules where timing side-channels are a primary threat.
3. Argon2id (Hybrid)
Argon2id is a hybrid variant that combines the strengths of both profiles. It splits the first pass over memory into two phases:
- The first half of the first pass behaves like Argon2i (data-independent) to secure the system against side-channel attacks during the initialization phase.
- The remainder of the execution behaves like Argon2d (data-dependent) to provide maximum defense against GPU and ASIC cracking.
- Use Case: Standard web application password hashing. Argon2id is the standard recommendation of the PHC and RFC 9106.
Comparing Architectures: Bcrypt vs. Argon2
| Feature | Bcrypt | Argon2id | | :--- | :--- | :--- | | Primary Hardness | Time (CPU-bound) | Memory & Time (RAM & CPU-bound) | | Max Memory Footprint| Fixed at $4\text{ KB}$ | Configurable ($1\text{ KiB}$ to $2^{32}\text{ KiB}$) | | Parallelization | Single-threaded | Native multi-threading ($1$ to $2^{24}$ threads) | | Side-channel Resistance| High (due to small size) | High (first phase is data-independent) | | ASIC/FPGA Resistance| Low | Extremely High | | Standard Specifications| De facto standard | RFC 9106 |
Argon2 Parameter Selection Methodology
To implement Argon2id safely, you must calibrate the parameters ($m$, $t$, $p$) based on your target hardware's limits. The objective is to maximize memory usage and execution time without impacting server throughput or user experience (target calculation time should ideally be between $100\text{ ms}$ and $500\text{ ms}$).
1. Determine Parallelism ($p$)
Set the parallelism parameter to twice the number of CPU cores available to the application process. For instance, on a 4-core server, set $p = 8$ or $p = 4$.
2. Determine Memory Cost ($m$)
Allocate as much RAM as your server can safely spare under peak concurrency loads. RFC 9106 recommends:
- High-security settings: $m = 65536$ ($64\text{ MiB}$)
- Medium-security settings: $m = 16384$ ($16\text{ MiB}$)
3. Determine Time Cost ($t$)
Once $p$ and $m$ are locked, run benchmark tests on your production hardware and increment the time cost $t$ until the average hash execution time reaches your target boundary (e.g., $150\text{ ms}$).
- For $m = 64\text{ MiB}$, $t = 3$ is a common baseline.
- For $m = 16\text{ MiB}$, $t = 4$ or higher is typical.
Implementation Reference (Node.js)
Below is an implementation of password hashing and verification using the native argon2 module in Node.js (which binds to the reference C implementation):
const argon2 = require('argon2');
/**
* Hashes a plaintext password using Argon2id with production settings
* @param {string} password - The raw user password
* @returns {Promise<string>} The encoded Argon2 hash string
*/
async function hashPassword(password) {
try {
const hash = await argon2.hash(password, {
type: argon2.argon2id, // RFC 9106 recommended hybrid mode
memoryCost: 65536, // 64 MiB of memory
timeCost: 3, // 3 passes over memory
parallelism: 4, // 4 concurrent threads
saltLength: 16 // 128-bit cryptographically secure salt
});
return hash;
} catch (err) {
throw new Error('Hashing execution failed: ' + err.message);
}
}
/**
* Verifies a password against an existing Argon2id hash
* @param {string} hash - The stored Argon2 hash string
* @param {string} password - The raw password input
* @returns {Promise<boolean>} Match result
*/
async function verifyPassword(hash, password) {
try {
// The library automatically parses parameters (m, t, p) from the hash format
return await argon2.verify(hash, password);
} catch (err) {
return false;
}
}
The Encoded Hash Structure
Argon2 outputs a structured text format containing all parameters, allowing future verification engines to automatically configure their runtime parameters:
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQxN2J5dGVz$cGFzc3dvcmRoYXNoZGF0YQ...
$argon2id$: Specifies the hybrid variant.v=19: Algorithm version (Version 1.3 / decimal 19).m=65536,t=3,p=4: The configured memory size, iterations, and thread count.c29tZXNhbHQxN2J5dGVz: Base64-encoded salt.cGFzc3dvcmRoYXNoZGF0YQ...: Base64-encoded digest payload.
Conclusion: When should you migrate?
If your application currently uses Bcrypt with a work factor of 10 or 12, your system is still reasonably secure against casual cracking. However, if you are building new authentication infrastructures, processing highly sensitive user credentials (such as financial portals, healthcare systems, or password managers), or updating legacy codebases, Argon2id is the correct choice. It provides configurable defense dimensions that future-proof your credentials against the inevitable advances of GPU and ASIC clustering.
Ready to use the engine?
Deploy our high-precision Password manifest for your professional workload. Fast, free, and privacy-encrypted.
Launch Argon2 Tool