DEV Community

EvvyTools
EvvyTools

Posted on

bcrypt vs. Argon2id: Choosing a Password Hashing Algorithm for Your Application

The password hashing algorithm your application uses matters more to real-world account security than the complexity rules you impose on users. A 60-bit-entropy password behind bcrypt can take thousands of years to crack offline. The same password behind MD5 falls in minutes. The storage layer is the part of password security you control as a developer.

This is not a complete survey of every algorithm -- it focuses on the practical choice between bcrypt (the incumbent) and Argon2id (the current best practice recommendation), with enough context to make an informed decision.

Why Password Hashing Is Different From Encryption

Encryption is reversible. Password hashing is intentionally not. The goal is to store a representation of the password that allows verification without allowing recovery. When a user submits a password, you hash it and compare the result to the stored hash. You never store or recover the original.

The additional requirement is that this hash function should be slow and expensive to compute repeatedly. Fast hash functions (SHA-256, MD5) can be computed billions of times per second, which is fine for integrity checking but catastrophic for password storage: if the database leaks, an attacker can test billions of guesses per second against the stolen hashes. The attacker does not need to access your system -- they work entirely offline with the leaked data.

Password-specific hashing algorithms are designed to be computationally expensive in a configurable way. The cost is calibrated so that a single hash computation takes tens to hundreds of milliseconds on server hardware -- imperceptible to a user logging in, but a rate-limiting factor that reduces an attacker's throughput by many orders of magnitude.

bcrypt

bcrypt was designed by Niels Provos and David Mazieres in 1999 specifically for password hashing. It has been in production use for over 25 years, is implemented in virtually every language ecosystem, and has accumulated a large amount of peer review and production hardening.

The cost factor is a single integer (usually labeled work_factor or rounds) that specifies 2^cost iterations. Cost 10 means 1,024 iterations. Cost 12 means 4,096 iterations. The recommended minimum as of 2025 is cost 12, which produces a hash time of roughly 200-300ms on typical server hardware.

bcrypt has two significant limitations worth knowing:

Password length is capped at 72 bytes. Input beyond 72 characters is silently truncated. For most user passwords this is irrelevant, but it means an attacker testing "correcthorsebatterystaple" and "correcthorsebatterystapl" against a bcrypt hash gets the same result if the first 72 characters match.

Memory usage is fixed at 4 KB. bcrypt predates memory-hard hashing. Modern attackers can run bcrypt at scale on GPUs because the low memory requirement allows many parallel instances. A cost-12 bcrypt hash takes about 200ms on CPU but attackers can run thousands of instances in parallel on GPU hardware.

The bcrypt Wikipedia article covers the algorithm's history and structure in detail.

Argon2id

Argon2 won the Password Hashing Competition in 2015. There are three variants: Argon2i (side-channel resistant), Argon2d (GPU resistant), and Argon2id (hybrid of both). Argon2id is the recommended variant for password hashing.

Argon2id is memory-hard: it requires a configurable amount of memory to compute a single hash. This matters because GPU-based attacks derive their throughput advantage from parallelism, and parallel instances each need their own memory allocation. A high memory cost limits the number of instances an attacker can run simultaneously on GPU hardware.

The parameters you configure:

  • memory (in KB): how much memory each hash computation uses. The current OWASP recommendation is 19 MB minimum (19456 KB).
  • iterations (time cost): how many passes over the memory. Minimum 2 for Argon2id.
  • parallelism: number of parallel threads. Typically set to match the number of CPU cores available.
  • hashLength: output length in bytes. 32 bytes (256 bits) is standard.
  • saltLength: 16 bytes minimum.

Lock mechanism secure padlock steel
Photo by fotshot on Pixabay

Side-by-Side Comparison

Property bcrypt Argon2id
Year 1999 2015
Memory-hard No (4 KB) Yes (configurable)
Password length limit 72 bytes No practical limit
GPU resistance Moderate Strong
OWASP recommendation Minimum (legacy) Preferred
Library availability Excellent (all languages) Good (improving)
Configuration complexity Low Medium

For new applications, Argon2id is the correct choice. For existing applications using bcrypt with cost 12, migration is advisable but not urgent -- bcrypt at cost 12 still provides reasonable protection, and the migration requires re-hashing on login (you cannot batch-rehash without knowing the plaintext passwords).

What to Use in Practice

The OWASP Password Storage Cheat Sheet provides specific parameter recommendations:

For Argon2id: memory 19 MB, iterations 2, parallelism 1 at minimum. If your server has more RAM available, increase memory to 64 MB or higher. Tuning guidance: the hash should take at least 500ms on your server. Increase iterations until you reach that threshold without causing user experience issues.

For bcrypt: cost factor 10 as an absolute minimum, 12 preferred. Tune upward if your hardware allows for hash times above 300ms without perceptible impact on login response time.

For systems where Argon2id is not yet available (older language runtimes, specific deployment environments), bcrypt at cost 12 remains a defensible choice. The practical attack gap between bcrypt-12 and Argon2id with recommended parameters is significant but not critical for most applications today.

Migrating Between Algorithms

If you are migrating an existing application from bcrypt to Argon2id, the standard approach is a lazy migration on login. When a user successfully authenticates, you verify their credential against the stored bcrypt hash, then immediately re-hash the plaintext with Argon2id and store the new hash. The old bcrypt hash is replaced without any disruption to the user.

You cannot proactively re-hash without the plaintext, so this migration happens gradually as users log in. Store an algorithm identifier alongside each hash so you can route verification to the correct function during the transition period. Most libraries store this in the hash string itself using a prefix format, which makes the routing straightforward.

The User-Facing Side of This

A well-configured hashing algorithm is necessary but not sufficient. The other side of account security is what happens before hashing: how users generate and manage their credentials.

From a developer's perspective, the recommendations from NIST SP 800-63B point toward accepting all printable characters, supporting long passwords (64+ characters), and checking submitted passwords against breach lists rather than imposing composition rules. The composition rules approach does not produce the randomness it implies -- users satisfy them in predictable ways that attackers specifically model.

The credential quality question and the storage quality question interact: a strong hashing algorithm protects all users better, but it particularly protects users with lower-entropy passwords who might otherwise be quickly cracked from a leaked database. A password that takes 10 minutes to crack against bcrypt-12 is adequately protected for most purposes; the same password against MD5 falls instantly.

For users, understanding the entropy their password actually provides -- and how that translates to crack time given realistic attack scenarios -- is valuable context. The companion article How Password Entropy Works and Why It Matters for Account Security covers the calculation in detail. EvvyTools has a Password Generator that shows entropy estimates and crack-time projections in real time, which is a useful way to develop intuition for what configuration choices actually cost an attacker.

Top comments (0)