Argon2 Explained: Memory-Hard Password Hashing
How Argon2 works — memory-hard password hashing, the Argon2d/i/id variants, the memory, time and parallelism parameters, and why it beats bcrypt and PBKDF2.
Argon2 is the modern default for storing passwords. It is a memory-hard function: it deliberately forces an attacker to commit large amounts of RAM, not just CPU cycles, to every guess. That single design choice is what makes it dramatically harder to crack at scale than the algorithms that came before it. This article walks through how Argon2 actually works, the three variants you will encounter, the parameters you have to tune, and how it compares to bcrypt, PBKDF2 and scrypt.
Why Argon2 Exists
Password hashing has a peculiar goal: it must be slow on purpose. A login should be fast enough for one user but punishing for an attacker who has stolen your database and wants to test billions of candidate passwords. If you are new to the underlying idea, our pillar article on how cryptographic hashing works covers the fundamentals of one-way functions and salting.
By the early 2010s, the existing options were showing their age. PBKDF2 and bcrypt slow things down by repeating computation, but they need very little memory — which means an attacker can build cheap, massively parallel hardware to brute-force them. To find a successor, the cryptographic community ran the Password Hashing Competition (PHC), an open, multi-year contest modeled on the AES and SHA-3 selection processes. In 2015, Argon2 was announced as the winner, designed by Alex Biryukov, Daniel Dinu and Dmitry Khovratovich at the University of Luxembourg.
What "Memory-Hard" Actually Means
The core insight behind Argon2 is that compute is cheap to parallelize, but memory is not.
A modern GPU has thousands of arithmetic cores. An ASIC or FPGA can be fabricated with even more dedicated hashing circuits. Against an algorithm like PBKDF2 that needs only a few hundred bytes of state, each of those cores can run an independent guess. The attacker's throughput scales almost linearly with how much silicon they can buy.
Memory tells a different story. RAM is expensive in two ways that matter to an attacker:
- Silicon area. On-chip memory occupies physical die area. If each parallel hashing unit needs its own large memory block, the number of units you can pack onto a chip collapses. The cost is no longer dominated by compute logic but by memory cells.
- Bandwidth. Even when memory exists, moving data between memory and compute units is a bottleneck. A function that constantly reads from a large, randomly-addressed array saturates memory bandwidth, and you cannot fix bandwidth limits simply by adding more cores.
By requiring each password evaluation to fill and repeatedly read a large array — say tens or hundreds of megabytes — Argon2 makes the memory the dominant cost. A GPU or ASIC attacker no longer gets cheap linear scaling, because they would have to attach a large, fast memory bank to every parallel unit. That is the structural advantage over bcrypt and PBKDF2.
How Argon2 Fills Memory
Argon2 works over a large matrix of blocks, each 1024 bytes (1 KiB). The total amount of memory is set by the parameters: the matrix is organized as p lanes (rows), each divided into segments, with the total number of blocks determined by the requested memory size m.
The process runs in three broad phases:
- Initialization. The password, salt, and parameters are hashed with Blake2b to produce a seed, which is used to fill the first blocks of each lane.
- Filling and mixing passes. Argon2 then iterates over the matrix, computing each new block from two inputs: the previous block in the same lane, and a referenced block chosen from somewhere earlier in the matrix. The new block is produced by a compression function
G, built on a Blake2b-derived permutation that mixes the two 1 KiB inputs together thoroughly. Because each block depends on an earlier referenced block, the whole array must be held in memory and cannot be recomputed cheaply on the fly without paying the memory cost. - Finalization. After
tpasses over the matrix, the final blocks of all lanes are XORed together and hashed once more to produce the output tag.
The number of passes t lets you increase cost without increasing memory: more passes mean more mixing over the same array. The interesting design question is how the referenced earlier block is chosen — and that is exactly where the three variants differ.
The Three Variants: Argon2d, Argon2i, Argon2id
Argon2 comes in three flavors that differ in how they decide which earlier block to reference.
Argon2d — data-dependent addressing
Argon2d chooses the reference block based on the contents of the previous block — the addressing is data-dependent. This maximizes resistance to GPU and time-memory tradeoff attacks, because the access pattern is unpredictable and an attacker cannot precompute or reorder the work. The drawback: because memory addresses depend on secret data, the access pattern can leak through side channels (for example, cache-timing observations) on a shared machine. Argon2d is best suited to settings where side-channel attacks are not a concern, such as cryptocurrency proof-of-work.
Argon2i — data-independent addressing
Argon2i chooses reference blocks using a pattern that depends only on public inputs (the pass number, lane, position), not on the secret. The addressing is data-independent, so it leaks nothing through memory-access side channels. This makes it the safe choice when an attacker might share hardware with you. The trade-off is that a predictable access pattern is somewhat more vulnerable to time-memory tradeoff attacks, so Argon2i typically needs more passes to reach equivalent strength.
Argon2id — the hybrid (recommended)
Argon2id combines both. It runs the first half of the first pass using data-independent addressing (like Argon2i), then switches to data-dependent addressing (like Argon2d) for the remainder. The first phase protects against side-channel leakage of the secret early on; the second phase restores the strong GPU and tradeoff resistance of Argon2d. This balance is why Argon2id is the recommended default for password hashing, and it is the variant called out in RFC 9106 as the primary choice.
The Parameters: m, t, p
Argon2 is tuned with three primary parameters:
m— memory cost (in KiB). The size of the memory matrix. This is the headline security knob, because memory is what defeats parallel hardware. Largermdirectly raises an attacker's per-guess cost.t— time cost (iterations). The number of passes over the matrix. Increases cost without increasing memory; useful when you want more work but have a fixed memory budget.p— parallelism (lanes). How many lanes can be computed in parallel, ideally matched to the number of CPU cores available to your defender during legitimate hashing.
Two more values complete the picture: the salt (unique per password, ideally at least 16 bytes) and the desired output length (tag size, commonly 32 bytes).
The tuning strategy is straightforward: pick the largest memory m your server can afford per concurrent login, set parallelism p to your available cores, then raise iterations t until a single hash takes a target amount of time — often somewhere in the range of tens to a few hundred milliseconds, depending on your latency budget.
The PHC String Format
Argon2 hashes are stored using the standard PHC string format, a self-describing encoding that packs the variant, version, parameters, salt and hash into one string. Because the parameters travel with the hash, you can verify a password without storing them separately — and you can raise parameters for new users without breaking old ones.
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJ...
| | | | |
| | | | └─ Base64 hash (the output tag)
| | | └─ Base64-encoded salt
| | └─ parameters: m = memory KiB, t = iterations, p = parallelism
| └─ version: v=19 means 0x13 (Argon2 version 1.3)
└─ variant: argon2id (or argon2i / argon2d)
The salt and hash are Base64-encoded without padding. Note that v=19 is the decimal form of 0x13, the current Argon2 version.
Security Guidance and Parameter Recommendations
The practical advice from OWASP and RFC 9106 converges on a few rules of thumb:
- Use Argon2id for new applications. It is the safe default unless you have a specific reason to choose another variant.
- Spend memory generously. Guidance typically points at configurations using tens of MiB of memory (commonly in the range of roughly 19 MiB up to many tens of MiB) combined with a small number of iterations (a few passes), with parallelism set to your cores. Treat these as starting points and benchmark on your own hardware rather than copying fixed numbers, because hardware and threat models change over time. Always verify against the current OWASP Password Storage Cheat Sheet before deploying.
- Use a unique random salt per password — every hash should be distinct even for identical passwords.
- Benchmark and re-tune. Pick parameters so that legitimate hashing stays within your latency budget, then revisit them as hardware improves.
For a broader walkthrough of choosing and migrating between algorithms, see our password hashing guide.
How Argon2 Compares
- vs. PBKDF2 — PBKDF2 is iteration-based and uses negligible memory, so it is not memory-hard and scales poorly against GPU and ASIC attackers. It remains relevant mainly for FIPS-constrained environments. See PBKDF2 and scrypt explained.
- vs. bcrypt — bcrypt is battle-tested and has a built-in cost factor, but it uses only a small, fixed 4 KiB of memory, which is not enough to seriously inconvenience modern parallel hardware. Read how bcrypt works for the details.
- vs. scrypt — scrypt is memory-hard and predates Argon2; it is a solid choice and is also covered in our PBKDF2 and scrypt article. Argon2 improves on it with cleaner separation of the time and memory parameters, the side-channel-resistant variants, and a formally analyzed design that won the PHC.
In short: bcrypt and PBKDF2 are not memory-hard; scrypt and Argon2 are. Among the memory-hard options, Argon2id is the current consensus default for new systems.
Try It Yourself
You can experiment with Argon2 without installing anything: generate an Argon2 hash in your browser. Hash Generator runs Argon2 entirely client-side — compiled from Rust to WebAssembly — so your password, salt and parameters never leave your machine and nothing is uploaded to a server. It is a quick way to see how changing m, t and p affects the resulting PHC string and the time each hash takes.
Conclusion
Argon2 won the Password Hashing Competition because it solved the problem its predecessors could not: making each password guess expensive in memory, not just compute, and thereby neutralizing the cost advantage of GPUs, FPGAs and ASICs. With Argon2id as a sensible default, three clear parameters to tune, and a self-describing PHC string for storage, it is the algorithm to reach for in new applications. When you are ready to see it in action, try the in-browser Argon2 generator and watch the parameters shape the output in real time.