DEV Community

Cover image for Why I Chose Argon2id Over the \"Faster\" Alternatives
Bijan
Bijan

Posted on Originally published at linkedin.com

Why I Chose Argon2id Over the \"Faster\" Alternatives

Argon2id is deliberately slow. That's not a limitation — it's the entire design goal. For key derivation, fast is a vulnerability, not a feature, and understanding why changed how I think about "performance" as a security property.

This is part 3 of a series documenting what I'm learning building CryptoGraphy.

The core trade-off: speed helps the attacker more than you

Derive a key in one microsecond, and an attacker with the right hardware can try billions of password guesses per second against a stolen hash or ciphertext. Every guess is basically free for them. Make derivation slow and memory-hungry, and that same attack — still technically possible — becomes expensive enough to be impractical at scale.

Here's the actual configuration in crypto.py:

def derive_key(master_password: str, salt: bytes) -> bytes:
    return hash_secret_raw(
        secret=master_password.encode("utf-8"),
        salt=salt,
        time_cost=3,
        memory_cost=65536,   # 64 MiB
        parallelism=4,
        hash_len=32,
        type=Type.ID,
    )
Enter fullscreen mode Exit fullscreen mode

memory_cost=65536 means each single derivation attempt requires 64MB of memory. time_cost=3 adds iteration on top of that. Individually these numbers look arbitrary. Together, they're the whole point.

Why memory cost specifically matters

Older key derivation functions like PBKDF2 are fast to attack on GPUs, because GPUs are built for exactly the kind of math PBKDF2 does: massively parallel, low-memory operations. A single high-end GPU can run an enormous number of parallel PBKDF2 guesses simultaneously, because each guess needs almost no memory.

Argon2 breaks that advantage on purpose. By forcing every single guess to allocate and use real memory (64MB, in this config), it caps how many guesses can run in parallel — GPUs have far less memory available per core than a CPU does, so a GPU that could run thousands of parallel PBKDF2 attempts can only run a small fraction of that many parallel Argon2 attempts before running out of memory. This is called being "memory-hard," and it specifically neutralizes the GPU cracking rigs that are the actual hardware attackers use at scale, not a theoretical edge case.

Why "id" specifically, not "i" or "d"

Argon2 actually comes in three variants:

  • Argon2d — maximizes resistance to GPU cracking, but is vulnerable to certain side-channel timing attacks because memory access patterns depend on the secret input.
  • Argon2i — designed to resist those side-channel attacks by using data-independent memory access, at some cost to GPU resistance.
  • Argon2id — a hybrid that runs Argon2i-style access for part of the process and Argon2d-style for the rest, aiming to get meaningful resistance to both attack classes at once.

That hybrid design is why Argon2id is the variant recommended by current guidance (including OWASP's password storage recommendations) over using Argon2i or Argon2d alone — it doesn't maximize either property individually, but it doesn't leave either one badly exposed either.

Tuning is a real trade-off, not a fixed answer

time_cost=3, memory_cost=65536, parallelism=4 aren't universal constants — they're a balance between security and usability. Crank memory and time cost up, and you get stronger resistance to offline cracking, but every legitimate login or decryption also gets slower and heavier on the server or device doing the deriving. Set them too low, and you're back to a derivation cheap enough to attack at scale. There's no single correct number here — it depends on your threat model and what hardware your legitimate users are deriving keys on.

The takeaway

Slow isn't a bug in a key derivation function — it's the mechanism. Argon2id spends real time and real memory on every single derivation specifically so that an attacker has to spend that same cost, multiplied by however many guesses they need to make. That's the trade a KDF is supposed to make, and it's a completely different goal from a general-purpose hash function like SHA-256, which is meant to be fast.


Next in this series: why encryption alone isn't enough, and what AES-GCM adds on top of confidentiality.

If you've tuned Argon2 or bcrypt cost parameters for a real production system, what trade-offs did you land on? I'd like to hear how people are actually setting these in practice.

Top comments (0)