bcrypt vs Argon2 vs scrypt for Passwords

Khimananda Oli 10 min read Database
bcrypt vs Argon2 vs scrypt for Passwords

By Khimananda Oli | Last reviewed: August 2026

Choosing between bcrypt vs Argon2 vs scrypt for passwords determines whether your user data survives a credential leak or becomes an immediate liability. While bcrypt remains the industry workhorse due to decades of cryptanalysis, modern memory-hard functions like Argon2id offer superior resistance against GPU and ASIC attacks that dominate the threat landscape in 2026. This guide cuts through theoretical benchmarks to provide actionable configuration parameters, migration strategies, and compliance-aligned recommendations for production environments.

bcryptCPU-Hard OnlyVulnerable to GPUConstant Time VerifyFixed Memory (4KB)Best for: Legacy / FIPSArgon2idMemory + CPU HardGPU/ASIC ResistantSide-Channel SafeConfigurable MemoryRecommended 2026scryptMemory-Hard (Legacy)GPU ResistantTiming Leak RiskHigh RAM OverheadBest for: Embedded/IoT
Security posture comparison across bcrypt vs Argon2 vs scrypt for passwords highlighting GPU resistance and memory hardness characteristics

How do bcrypt vs Argon2 vs scrypt for passwords differ in attack resistance?

The fundamental distinction lies in what resource each algorithm makes expensive for attackers. Understanding these mechanical differences directly informs your risk model, especially when designing infrastructure that must pass audits like SOC 2 or ISO 27001 where cryptographic controls are scrutinized.

bcrypt: The CPU-bound veteran

bcrypt derives from the Blowfish cipher's expensive key setup. Its security relies entirely on computational cost via the work factor parameter. In practice, this means verification time doubles with each increment. A common mistake I see during server hardening audits is teams leaving bcrypt at the default cost of 10. On modern AMD EPYC or AWS Graviton4 instances, cost 10 yields verification times under 50ms — far too fast to deter offline cracking. You should target cost 12–14, achieving 250–500ms per hash on production hardware.

bcrypt’s critical weakness is its fixed 4KB memory footprint. Modern GPUs can parallelize thousands of bcrypt computations simultaneously because they fit entirely in fast SRAM caches. An attacker with eight RTX 4090s achieves throughput that renders cost 12 trivially brute-forceable for high-value targets.

Argon2id: Memory-hard with hybrid protection

Argon2 won the Password Hashing Competition in 2015 specifically to address bcrypt’s memory limitation. The "id" variant combines Argon2i’s data-independent memory access pattern (resistant to side-channel timing attacks) with Argon2d’s data-dependent pattern (resistant to GPU tradeoff attacks). This hybrid approach makes it the strongest general-purpose choice.

Memory hardness forces attackers to allocate substantial RAM per parallel thread. Where a GPU might run 10,000 concurrent bcrypt hashes, it may only manage 50–100 concurrent Argon2id hashes at equivalent security levels. This economic asymmetry is what protects breached credential databases in 2026.

scrypt: Memory-hard but aging

Colin Percival designed scrypt in 2009 for Tarsnap backup encryption. It pioneered memory-hardness using sequential memory reads that resist parallelization. However, scrypt lacks Argon2’s hybrid mode, making certain implementations vulnerable to cache-timing side channels. Additionally, scrypt’s memory access pattern creates higher constant-factor overhead on legitimate servers without proportionally increasing attacker costs compared to Argon2id.

What are the correct configuration parameters for each algorithm?

Theoretical superiority means nothing with misconfigured parameters. I’ve audited systems running Argon2id with 64MB memory that still failed penetration tests because iteration counts were too low. Always benchmark on your actual production hardware — never copy parameters from documentation examples.

bcrypt cost factor calibration

Target 250–500ms verification latency for interactive logins. Measure on your slowest production instance type:

<?php
// PHP example: Benchmark bcrypt cost on target hardware
$target_ms = 300;
for ($cost = 10; $cost <= 16; $cost++) {
    $start = hrtime(true);
    password_hash('benchmark-test', PASSWORD_BCRYPT, ['cost' => $cost]);
    $elapsed_ms = (hrtime(true) - $start) / 1e6;
    echo "Cost {$cost}: {$elapsed_ms}ms\n";
    if ($elapsed_ms >= $target_ms) break;
}
// Typical 2026 result: Cost 13 ≈ 320ms on AWS c7g.xlarge
  • Minimum acceptable (2026): Cost 12 (~150ms on modern hardware)
  • Recommended interactive: Cost 13–14 (250–500ms)
  • Background/API tokens: Cost 15–16 (acceptable latency tolerance)

The OWASP Password Storage Cheat Sheet updated their guidance in late 2025. Start here and adjust based on your server’s available RAM:

// Node.js example using @node-rs/argon2 (native bindings, no WASM overhead)
import { hash, verify } from '@node-rs/argon2';

const hashed = await hash(password, {
  algorithm: 'argon2id',      // Hybrid mode — always prefer over argon2i/d
  memoryCost: 65536,          // 64 MiB minimum; increase to 128-256 MiB if RAM allows
  timeCost: 3,                // Iterations; increase if memory is constrained
  parallelism: 4,             // Match vCPU count of smallest prod instance
  outputLen: 32               // 256-bit output sufficient for all use cases
});

// Verification uses same parameters embedded in hash string
const valid = await verify(hashed, candidatePassword);

For high-security contexts (admin accounts, financial systems), increase memoryCost to 131072 (128 MiB) and timeCost to 4. Ensure your application server has at least 4× the per-request memory allocation available to prevent swapping during peak load.

scrypt legacy tuning

If you must maintain scrypt (e.g., existing cryptocurrency wallets or embedded devices), use N=32768 (2^15), r=8, p=1 as a 2026 baseline. This consumes approximately 32 MiB per hash. Avoid increasing p (parallelism) beyond 1 unless you’ve validated timing attack mitigations in your specific library implementation.

New System?FIPS 140-3 Required?YESNOUse bcrypt (Cost 12+)Embedded / IoT Device?YESNOUse scrypt (N=32768)Use Argon2idAlways store algorithm + paramsin hash string for future migration
Decision framework for bcrypt vs Argon2 vs scrypt for passwords based on compliance requirements and deployment environment

When should you choose bcrypt over newer algorithms?

Despite Argon2id’s technical superiority, bcrypt remains the correct choice in specific operational contexts. Dismissing it outright ignores real-world compliance and interoperability constraints that govern production systems.

FIPS 140-3 and government compliance

As of 2026, Argon2 and scrypt are not approved under FIPS 140-3. If your system processes US federal data, handles HIPAA-covered entities requiring FIPS validation, or operates in regulated Nepali financial sectors adhering to NRB directives referencing international standards, bcrypt (via validated PBKDF2-SHA256 modules) may be your only compliant option. Always verify with your QSA or assessor before deploying non-FIPS algorithms in scoped environments.

Cross-platform portability requirements

bcrypt has native implementations in every major language and OS dating back 25 years. If you’re building firmware, supporting legacy PHP 7.x installations, or integrating with systems where adding libargon2 is operationally prohibitive, bcrypt’s ubiquity reduces supply chain risk. I’ve seen teams delay security patches for months waiting for Argon2 bindings on obscure platforms — sometimes the “weaker” algorithm ships faster and more reliably.

Predictable resource consumption

bcrypt’s fixed 4KB memory usage eliminates a class of denial-of-service vulnerabilities. With Argon2id configured for 128 MiB, a single malicious actor can exhaust server memory with dozens of concurrent login attempts. If you cannot implement robust rate limiting or request queuing (common in shared hosting or legacy architectures), bcrypt’s predictable footprint provides inherent DoS resilience.

How do you migrate existing password hashes without forcing resets?

Migration is where most teams fail. Never rehash all passwords at once or force mass resets — both create support nightmares and user churn. Implement transparent upgrade-on-login instead.

  1. Add algorithm detection to authentication middleware. Parse the stored hash prefix ($2y$ for bcrypt, $argon2id$ for Argon2) to determine current algorithm.
  2. Verify against stored hash using original algorithm. Authentication succeeds regardless of hash version.
  3. On successful auth, check if rehash needed. Compare stored parameters against current policy. PHP’s password_needs_rehash() automates this; other languages require manual comparison.
  4. Rehash plaintext password with new algorithm/params. Update database record atomically within the same transaction as session creation.
  5. Log migration metrics. Track percentage of upgraded hashes weekly. After 90 days, investigate remaining legacy hashes (inactive accounts, service principals).
# Python/Django example: Transparent migration decorator
def authenticate_with_upgrade(username, password):
    user = User.objects.get(username=username)
    
    # Step 1: Detect and verify with current algorithm
    if user.password.startswith('$argon2id$'):
        valid = argon2.verify(user.password, password)
        needs_upgrade = False
    elif user.password.startswith('$2y$'):
        valid = bcrypt.checkpw(password.encode(), user.password.encode())
        # Step 3: Check if bcrypt cost below current policy
        stored_cost = int(user.password.split('$')[2])
        needs_upgrade = stored_cost < CURRENT_BCRYPT_COST
    else:
        return None  # Unknown format
    
    if not valid:
        return None
    
    # Step 4: Rehash with Argon2id if needed
    if needs_upgrade or not user.password.startswith('$argon2id$'):
        new_hash = argon2.hash(password, 
                              type=argon2.Type.ID,
                              memory_cost=65536,
                              time_cost=3,
                              parallelism=4)
        user.password = new_hash
        user.save(update_fields=['password'])
        metrics.increment('password_hash_upgraded')
    
    return user

This pattern works identically for bcrypt-to-bcrypt cost increases, scrypt-to-Argon2id migrations, or any combination. The key insight: you control the migration timeline through user activity, not deployment schedules. For detailed secrets handling during such transitions, review Kubernetes secrets management done right to ensure new hashes aren’t exposed in logs or environment variables.

What performance impact does each algorithm have on production systems?

Password hashing is intentionally slow, but uncontrolled slowness causes cascading failures. Understanding throughput characteristics prevents capacity planning disasters.

Metricbcrypt (cost 13)Argon2id (64MiB/3iter)scrypt (N=32768/r=8/p=1)
Single-thread latency~300ms~280ms~350ms
Memory per request4 KB64 MB32 MB
Max concurrent (8GB RAM)2000+~100~200
GPU attack efficiencyVery HighVery LowLow
DoS amplification riskLowHighModerate
FIPS 140-3 eligibleYes (PBKDF2 mode)NoNo

The memory column explains why Argon2id requires careful capacity planning. On a typical Kubernetes pod with 2GB RAM limit, you can serve roughly 25–30 concurrent Argon2id verifications before OOM kills trigger. Configure resource limits and requests to account for peak authentication bursts, not average load. Set memory requests to accommodate your expected concurrent login rate plus 30% headroom.

For high-throughput APIs, consider separating authentication from authorization. Hash passwords with Argon2id at login, then issue short-lived JWTs or opaque tokens verified via HMAC-SHA256 (microsecond-scale) for subsequent requests. This gives you strong password storage without per-request memory pressure. Monitor authentication latency as a golden signal alongside error rates and saturation, as outlined in the four golden signals of monitoring.

User Login(Plaintext PW)Detect AlgorithmParse hash prefix$2y$ → bcrypt$argon2id$ → Argon2Verify HashUse detected algo✓ Auth Success✗ Reject + LogNeeds Upgrade?Compare paramsvs current policyYESRehash Argon2idUpdate DB atomicallyEmit metric eventSession Created(User unaware)
Zero-friction migration workflow for upgrading bcrypt vs Argon2 vs scrypt for passwords during normal authentication flow

Final recommendations for secure password storage in 2026

Your choice among bcrypt vs Argon2 vs scrypt for passwords should reflect your actual threat model, compliance obligations, and operational maturity — not Hacker News consensus. For greenfield projects outside FIPS scope, deploy Argon2id with 64–128 MiB memory, 3–4 iterations, and parallelism matching your vCPU count. For regulated environments or resource-constrained deployments, bcrypt at cost 13+ remains defensible and widely supported. Reserve scrypt for niche embedded scenarios where Argon2 libraries are unavailable.

Regardless of algorithm, enforce three non-negotiable practices: store hashes with embedded parameters (never separate config), implement transparent upgrade-on-login for migrations, and rate-limit authentication endpoints to prevent both brute force and memory exhaustion attacks. Pair this with comprehensive observability — track hash verification latency, upgrade rates, and failure modes as first-class metrics.

If you’re evaluating your current password storage posture or planning a migration across heterogeneous systems, reach out to discuss your specific architecture. I help teams align cryptographic choices with compliance requirements, infrastructure constraints, and realistic threat models — ensuring your authentication layer is secure, performant, and audit-ready.

Frequently Asked Questions

Argon2id is the default and recommended choice for Laravel 12 applications. It resists both GPU and side-channel attacks better than bcrypt while maintaining configurable memory costs suitable for modern cloud infrastructure deployments.

Yes, bcrypt remains secure with cost factor 12 or higher. Its 72-byte input limit and lack of memory hardness make it less ideal than Argon2id against specialized hardware, but it has decades of proven cryptanalysis without practical breaks.

Argon2id combines data-independent and data-dependent memory access patterns. This hybrid approach protects against side-channel attacks like Argon2i while maintaining GPU resistance like Argon2d, making it the preferred variant for password hashing in production systems.

Use cost factor 12 minimum, targeting 250ms verification time on your production hardware. Test with benchmark commands to balance security and latency, adjusting upward as compute performance improves over time.

Scrypt lacks standardized parameter tuning guidance and has fewer audited implementations than Argon2. Most frameworks default to Argon2id or bcrypt, leaving scrypt primarily in legacy systems or specific cryptocurrency applications rather than general web authentication.

Yes, implement dual-hash verification checking Argon2id first then falling back to bcrypt. Rehash passwords with Argon2id upon successful login using your framework's built-in rehashing middleware to gradually upgrade without forcing password resets.

Start with 64MB memory cost, 4 iterations, and parallelism matching your CPU cores. Benchmark on production-equivalent hardware to ensure verification stays under 300ms while maximizing memory usage your server can sustain during peak concurrent logins.

Yes, bcrypt silently ignores bytes beyond position 72. Pre-hash long passwords with SHA-256 before bcrypt if you must support them, though Argon2id handles arbitrary lengths natively without this limitation or truncation risks.

Use PHP's password_hash with timing measurements or dedicated tools like hashcat benchmark mode. Target 200-300ms per verification to balance user experience against brute-force resistance, testing under realistic concurrent load conditions.

Yes, Argon2id requires PHP 7.3 or later with libsodium extension enabled. All currently supported PHP versions include native support, and Laravel auto-detects availability during installation to configure appropriate defaults.

Excessive memory settings cause out-of-memory errors during verification, creating denial-of-service vulnerabilities. Monitor peak memory usage under load and configure limits below your worker process memory ceiling to prevent crashes during authentication spikes.

No, never add manual salts. All three algorithms generate cryptographically secure random salts internally and embed them in the output hash string. Manual salting introduces implementation errors without providing additional security benefits.

Memory-constrained instances like AWS t4g.micro require lower Argon2 memory costs than larger instances. Benchmark on your exact instance type since shared tenancy and burstable performance create variable timing that impacts safe parameter configuration.

No, Argon2id's memory-hardness significantly increases GPU attack costs compared to bcrypt. While specialized ASICs may eventually reduce this advantage, Argon2id currently provides superior resistance against parallelized hardware attacks available in 2026.

NIST SP 800-63B recommends memory-hard functions like Argon2 for new deployments. PCI DSS and SOC2 accept bcrypt with adequate cost factors, but Argon2id satisfies stricter requirements and future-proofs against evolving regulatory guidance through 2026.