Aller au contenu
← Back to blog

Argon2id vs bcrypt vs scrypt — Which Password Hashing Function Should You Use in 2026?

argon2idbcryptscryptcryptographysecuritykey-derivationowasppassword-hashing

Argon2id vs bcrypt vs scrypt — Which Password Hashing Function Should You Use in 2026?

If you're building anything that stores user passwords, you have to choose a password hashing function. The three realistic options in 2026 are Argon2id, bcrypt, and scrypt. Each has different security properties, performance characteristics, and threat models.

This guide compares them head-to-head, with benchmarks, OWASP recommendations, and copy-paste code examples in Node.js, Python, and Rust.

What Is a Password Hashing Function?

A password hashing function is a slow, salted, one-way cryptographic function designed specifically for passwords. It is:

  • Slow on purpose (millions to billions of times slower than SHA-256) to make brute force expensive
  • Salted to prevent rainbow table attacks
  • One-way so the original password cannot be recovered

General-purpose hash functions (MD5, SHA-256) are not suitable for passwords — they are designed to be fast, which is the opposite of what you want.

The Three Contenders

bcrypt (1999)

Designed by Niels Provos and David Mazières. Based on the Blowfish cipher. CPU-hard by design (uses iterative key schedule).

// Node.js

const bcrypt = require('bcrypt'); const hash = await bcrypt.hash(password, 12); // cost factor 12 const ok = await bcrypt.compare(password, hash);

# Python

import bcrypt hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) ok = bcrypt.checkpw(password.encode(), hash)

// Rust

use bcrypt::{hash, verify, DEFAULT_COST}; let hash = hash(password, DEFAULT_COST)?; let ok = verify(password, &hash)?;

scrypt (2009)

Designed by Colin Percival. Memory-hard — uses a configurable amount of RAM to make GPU/ASIC attacks expensive. Powers Tarsnap and many crypto wallets.

// Node.js (built-in)

const crypto = require('crypto'); const salt = crypto.randomBytes(16); const hash = crypto.scryptSync(password, salt, 64, { N: 16384, r: 8, p: 1 });

# Python

import hashlib salt = os.urandom(16) hash = hashlib.scrypt(password.encode(), salt=salt, n=16384, r=8, p=1, dklen=64)

Argon2id (2015)

Winner of the Password Hashing Competition. Three variants: Argon2d (data-dependent, fastest), Argon2i (data-independent, side-channel resistant), and Argon2id (hybrid — the recommended variant).

// Node.js

const argon2 = require('argon2'); const hash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 65536, // 64 MiB timeCost: 3, parallelism: 4, }); const ok = await argon2.verify(hash, password);

# Python

from argon2 import PasswordHasher ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4) hash = ph.hash(password) ok = ph.verify(hash, password)

// Rust

use argon2::{Argon2, PasswordHasher, password_hash::{SaltString, rand_core::OsRng}};

let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();

Technical Comparison

PropertybcryptscryptArgon2id
Year199920092015
Memory-hardNoYesYes
Parallelism tunableNoYes (blockmix p)Yes (p param)
Iterations tunableYes (cost)Yes (N)Yes (t param)
Side-channel resistantPartialYes (configurable)Yes (Argon2i mode)
Max password length72 bytes (then silently truncated)UnlimitedUnlimited
Standardizedde factoRFC 7914RFC 9106
OWASP recommended (2026)Acceptable fallbackAcceptablePreferred
GPU attack costLow (cheap to attack)HighHigh
ASIC attack costLowMedium-HighHigh

Performance Benchmarks (2026, single core, modern x86)

AlgorithmMemoryTimeHashes/sec on RTX 4090
bcrypt (cost 12)4 KB~250 ms~4,000
scrypt (N=2^15, r=8, p=1)32 MB~200 ms~50
Argon2id (64MB, t=3, p=4)256 MB~300 ms~5
Argon2id (19MB, t=2, p=1)19 MB~50 ms~200
SHA-256 (for comparison)0< 1 µs10 billion+

The key insight: Argon2id is 3 orders of magnitude harder to attack on GPU than bcrypt, because of its memory-hardness.

Threat Models

Why bcrypt is no longer the best choice

bcrypt's 72-byte password truncation is a real problem. If your user picks a passphrase like "correct horse battery staple", bcrypt only hashes the first 72 bytes (about 60 characters). The rest is ignored. Combined with bcrypt's tiny memory footprint (4 KB), an attacker with a stack of GPUs can crack bcrypt hashes at thousands of guesses per second per GPU.

Why scrypt is good but rarely used

scrypt was the first practical memory-hard KDF and is excellent in theory. But:

  • The N parameter is the iteration count, but the r (block size) and p (parallelism) parameters are less intuitive
  • Few production-quality libraries have a stable, audited implementation
  • It's harder to configure safely than Argon2id

Why Argon2id wins

  1. OWASP-recommended since 2017
  2. Three orthogonal parameters (memory, time, parallelism) that can be tuned independently
  3. Hybrid mode (id) combines Argon2d's GPU resistance with Argon2i's side-channel resistance
  4. Standardized as RFC 9106
  5. Memory-hard by construction, not as a configuration option

OWASP Password Storage Cheat Sheet (2026)

SettingMemory (KiB)IterationsParallelismUse case
Minimum19,456 (19 MiB)21Interactive login (under 1 second)
Recommended65,536 (64 MiB)34Standard web application
High security262,144 (256 MiB)48Sensitive data, admin accounts

For bcrypt, OWASP recommends a minimum cost factor of 12 (rising to 13+ over time).

Common Mistakes

1. Using a fast hash (MD5, SHA-1, SHA-256)

// NEVER DO THIS

const hash = crypto.createHash('sha256').update(password).digest('hex');

This is hashing at gigabytes per second. An attacker with a single GPU will crack your entire user database in days.

2. Not salting

Without a unique random salt per user, an attacker uses rainbow tables — pre-computed hashes for billions of common passwords.

// Wrong: same salt for everyone

const hash = bcrypt.hash(password, '$2b$10$commonSaltForAllUsers');

// Right: random salt per user (handled automatically by bcrypt/argon2 libs) const hash = bcrypt.hash(password, 12);

3. Re-hashing on every login

Some developers re-hash the password on every successful login, thinking it adds security. It doesn't — it just wastes CPU and creates race conditions in multi-threaded servers. Hash once, store the parameters, verify on login.

4. Storing the hash in a field that's too short

If you use bcrypt, the hash is exactly 60 characters. If you use Argon2id, it's 80-100+ characters depending on parameters. Make sure your database column is large enough.

-- PostgreSQL

ALTER TABLE users ADD COLUMN password_hash TEXT; -- not VARCHAR(50)

5. Using outdated cost factors

bcrypt cost 10 was reasonable in 2010. In 2026, you need cost 12 minimum, 13+ preferred. Argon2id parameters should be revisited every 2 years as hardware gets faster.

Migration Strategy: bcrypt → Argon2id

You don't need to migrate all users at once. Use a "lazy migration" approach:

async function verifyAndMigrate(password, storedHash) {

if (await argon2.verify(storedHash, password)) { // Check if it was an old bcrypt hash if (storedHash.startsWith('$2b$') || storedHash.startsWith('$2a$')) { const newHash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 65536, timeCost: 3, parallelism: 4, }); await db.updateUserHash(userId, newHash); } return true; } return false; }

The next time a user logs in successfully, their password is silently re-hashed with Argon2id. After 90 days, 90%+ of active users are migrated.

How VaultKeepR Uses Argon2id

VaultKeepR uses Argon2id as the key derivation function for your master vault key. The parameters are tuned for modern hardware (64 MiB memory, 3 iterations, 4-way parallelism) and re-evaluated every release. Combined with XChaCha20-Poly1305 for vault encryption, this gives you state-of-the-art security without the legacy compromises of bcrypt.

→ Read our detailed crypto stack | → Why VaultKeepR is zero-knowledge

Final Recommendation

Use caseRecommendation
New application in 2026Argon2id with OWASP-recommended parameters
Existing bcrypt systemKeep bcrypt for now, migrate lazily to Argon2id
Crypto wallet / hardware key derivationArgon2id with high memory (256 MiB+)
IoT / embedded devicescrypt with conservative parameters
Maximum performance (you know what you're doing)Argon2id, tuned for your specific hardware

Bottom line: In 2026, if you're starting a new project, use Argon2id. There is no good reason to choose bcrypt for new code, and even scrypt is showing its age. Argon2id is the modern, OWASP-recommended, future-proof choice.


Need help auditing your password storage implementation? Get in touch — we do security reviews for open-source projects.

Share𝕏in

Ready to take control of your passwords?

VaultKeepR is the first decentralized password manager. Zero-knowledge. Wallet-native. Yours.

Try VaultKeepR →