Argon2 vs bcrypt for Password Hashing: Performance and Security Comparison

Argon2 provides superior memory-hard security with configurable parameters that resist GPU/ASIC attacks, while bcrypt offers simpler CPU-bound hashing suitable for low-resource environments, though it lacks memory hardness and scalability against modern hardware.

Python’s standard library no longer includes built-in password hashing utilities—the crypt module was removed in Python 3.13 according to Doc/library/crypt.rst【/cache/repos/github.com/python/cpython/main/Doc/library/crypt.rst†L1-L7】. The official documentation now directs developers to third-party libraries, specifically argon2-cffi and bcrypt, as modern replacements【/cache/repos/github.com/python/cpython/main/Doc/library/crypt.rst†L14-L18】. Understanding the architectural differences between these algorithms is essential for selecting the appropriate defense for your application’s threat model.

Algorithm Architecture and Design Philosophy

Argon2: Memory-Hard Winner of the Password Hashing Competition

Argon2 emerged as the winner of the 2015 Password Hashing Competition, designed specifically to resist high-performance hardware attacks. The algorithm provides two primary variants:

  • Argon2i: Uses data-independent memory access patterns to protect against side-channel attacks
  • Argon2id: Offers a balanced approach, combining data-independent and data-dependent memory access

As implemented in argon2-cffi, this algorithm allows developers to specify exact memory budgets, forcing attackers to allocate equivalent RAM resources rather than simply utilizing parallel GPU cores.

bcrypt: The Established CPU-Bound Standard

bcrypt, based on the Blowfish cipher and introduced in 1999, has served as the industry standard for decades. Unlike Argon2, bcrypt is purely CPU-hard—it performs computationally expensive key scheduling iterations without requiring significant memory allocation. The algorithm uses a fixed memory footprint of approximately 4 MiB regardless of configuration, and its only tunable parameter is the number of rounds (logarithmic cost factor).

Memory Hardness and Resistance to Hardware Attacks

The fundamental distinction between these algorithms lies in their approach to memory hardness, which determines resistance to GPU and ASIC cracking.

Argon2 implements strong memory hardness through configurable parameters. When you set memory_cost=65536 (64 MiB), legitimate servers and attackers alike must allocate that exact memory amount for each hash computation. This requirement neutralizes the advantage of GPUs, which contain thousands of cores but limited memory per core. Attackers cannot simply rent GPU time on cloud platforms to crack passwords efficiently; they must invest in expensive high-memory hardware.

bcrypt, conversely, exhibits weak memory hardness. Its fixed ~4 MiB memory requirement is trivial by modern standards and fits comfortably within GPU shared memory. While bcrypt remains resistant to rainbow table attacks through its salt and cost factor, it cannot prevent parallelized GPU cracking attempts. An attacker with a modern GPU array can test billions of bcrypt hashes per second, whereas Argon2 with high memory costs reduces this to thousands or millions, dramatically increasing the time required to crack credential databases.

Configurable Parameters and Tuning Flexibility

Argon2 Parameters

The argon2-cffi library exposes three critical tuning knobs in Doc/library/crypt.rst recommendations:

  • time_cost: Number of iterations (default typically 2-3)
  • memory_cost: Memory usage in KiB (e.g., 65536 for 64 MiB)
  • parallelism: Number of parallel threads (usually matching CPU core count)

This granularity allows security teams to increase memory costs as hardware improves without changing algorithms, future-proofing applications against advancing cracking technology.

bcrypt Parameters

The bcrypt library offers a single parameter:

  • rounds: Logarithmic cost factor (default 12, meaning 2^12 iterations)

While simple to configure, this limitation means bcrypt cannot leverage increasing RAM availability in modern servers to improve security. The only way to strengthen bcrypt against faster hardware is to increase rounds, which linearly increases CPU time but never addresses the GPU parallelization vulnerability.

Performance Benchmarks and Latency Characteristics

Real-world performance varies significantly based on configuration, but typical defaults reveal important trade-offs:

Argon2 with argon2id, time_cost=2, memory_cost=65536 (64 MiB), and parallelism=4 typically requires approximately 200 milliseconds per hash on a modern 4-core CPU. This latency scales linearly with memory allocation—doubling the memory budget doubles the computation time, creating a direct cost for attackers.

bcrypt with 12 rounds typically completes in approximately 100 milliseconds on equivalent hardware. While faster for legitimate authentication, this speed advantage benefits attackers equally. bcrypt’s performance scales only with CPU clock speed and core count, making it vulnerable to specialized cracking rigs.

For high-throughput applications, Argon2’s higher latency may require connection pooling or asynchronous processing, whereas bcrypt’s lower overhead fits better in resource-constrained environments like IoT devices or legacy embedded systems.

Security Guarantees and Threat Model Considerations

Side-Channel Resistance

Argon2i specifically addresses side-channel attacks through data-independent memory access patterns, preventing cache-timing attacks that could leak password information. Argon2id provides hybrid protection suitable for most applications. bcrypt lacks specific side-channel protections beyond standard constant-time comparison requirements.

GPU and ASIC Resistance

Argon2’s memory hardness fundamentally alters the economics of password cracking. According to the architectural analysis in the CPython documentation migration guides, forcing attackers to use high-memory hardware increases cracking costs by orders of magnitude compared to bcrypt, which runs efficiently on standard GPU arrays.

Algorithm Longevity

The Python 3.13 removal of the crypt module and explicit recommendation of Argon2 in Doc/whatsnew/3.13.rst signals industry confidence in Argon2 as the modern standard. bcrypt remains secure for legacy compatibility but represents previous-generation protection.

Implementation in Python

Using argon2-cffi

The CPython documentation specifically recommends argon2-cffi as the replacement for removed standard library functionality. Here is a production-ready implementation:


# argon2_example.py

from argon2 import PasswordHasher

# Configure memory (64 MiB), time cost (2 iterations), parallelism (4 threads)

ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=4, hash_len=32)

def hash_password(pw: str) -> str:
    return ph.hash(pw)

def verify_password(pw: str, hashed: str) -> bool:
    try:
        return ph.verify(hashed, pw)
    except Exception:
        return False

# Example usage

if __name__ == "__main__":
    pwd = "S3cureP@ssw0rd!"
    stored = hash_password(pwd)
    print("Stored hash:", stored)
    print("Verified:", verify_password(pwd, stored))

This implementation uses argon2id by default, providing balanced protection against both side-channel and GPU attacks. The memory_cost=65536 parameter (64 MiB) represents a reasonable default for modern web servers, though high-security applications may increase this to 512 MiB or higher.

Using bcrypt

For environments where memory constraints prevent Argon2 adoption, the bcrypt library provides a straightforward alternative:


# bcrypt_example.py

import bcrypt

def hash_password(pw: str, rounds: int = 12) -> bytes:
    salt = bcrypt.gensalt(rounds=rounds)
    return bcrypt.hashpw(pw.encode("utf-8"), salt)

def verify_password(pw: str, hashed: bytes) -> bool:
    return bcrypt.checkpw(pw.encode("utf-8"), hashed)

# Example usage

if __name__ == "__main__":
    pwd = "S3cureP@ssw0rd!"
    stored = hash_password(pwd)
    print("Stored hash:", stored.decode())
    print("Verified:", verify_password(pwd, stored))

Note that bcrypt’s rounds parameter uses a logarithmic scale—each increment doubles the computation time. The default of 12 rounds provides approximately 100ms latency on modern hardware, but security-conscious applications should monitor cracking hardware advancements and increase this value periodically.

When to Choose Which Algorithm

Select Argon2 (argon2-cffi) when:

  • You operate web services or authentication backends with adequate server memory (64MB+ per hash)
  • Resistance to GPU/ASIC cracking is a critical threat model requirement
  • You need configurable parameters to future-proof against hardware advances
  • Side-channel attack resistance is necessary (use Argon2i or Argon2id)

Select bcrypt when:

  • Deploying to IoT devices, embedded systems, or environments with severe memory constraints (<16MB available)
  • Maintaining compatibility with legacy bcrypt hash databases
  • Operating high-throughput systems where ~100ms latency per hash is unacceptable and memory allocation is restricted
  • You require a simpler configuration model with only one tuning parameter

Summary

  • Argon2 provides superior memory-hard security through configurable RAM usage, effectively neutralizing GPU and ASIC cracking advantages, but requires 64MB+ memory per hash and incurs ~200ms latency.
  • bcrypt offers CPU-bound hashing with minimal memory footprint (~4MB) and faster execution (~100ms), making it suitable for constrained devices but vulnerable to parallel hardware attacks.
  • Python 3.13 removed the standard crypt module, officially recommending argon2-cffi and bcrypt as replacements in Doc/library/crypt.rst and Doc/whatsnew/3.13.rst.
  • Choose Argon2 for high-security web applications with adequate memory; choose bcrypt for legacy compatibility or memory-constrained IoT environments.

Frequently Asked Questions

Is Argon2 actually more secure than bcrypt for password hashing?

Yes, Argon2 is generally considered more secure against modern hardware attacks because it is memory-hard, requiring attackers to allocate significant RAM (typically 64MB or more) per hash attempt. This design prevents efficient GPU and ASIC cracking, whereas bcrypt's fixed ~4MB memory footprint allows attackers to test billions of hashes per second using parallel graphics cards. However, both algorithms remain cryptographically secure when properly configured; Argon2 simply provides better resistance to specialized cracking hardware.

Why did Python remove the crypt module and recommend Argon2?

The crypt module was removed in Python 3.13 because it relied on the underlying operating system's crypt() implementation, which varies across platforms and often provides outdated, insecure hashing algorithms. According to Doc/library/crypt.rst, the module was deprecated due to "the insecure and obsolete nature of the underlying crypt() implementation"【/cache/repos/github.com/python/cpython/main/Doc/library/crypt.rst†L1-L7】. The documentation explicitly recommends argon2-cffi and bcrypt as modern, maintained alternatives that provide consistent, secure behavior across all platforms.

How much memory should I allocate for Argon2 in production?

For web applications and authentication servers, allocate 64MB (memory_cost=65536) as a minimum baseline, with 128MB to 512MB for high-security applications. The OWASP guidelines recommend at least 64MB to ensure GPU resistance, while financial or government applications may use 1GB or more. However, never exceed available RAM per worker process—if running 100 concurrent authentication workers on a server with 8GB RAM, allocating 128MB per hash would cause memory exhaustion. Always benchmark your specific deployment environment and leave headroom for application overhead.

Can I migrate existing bcrypt hashes to Argon2 without resetting passwords?

Direct migration without user interaction is impossible because bcrypt and Argon2 use fundamentally different hashing algorithms with incompatible output formats. You cannot "re-hash" a bcrypt hash into Argon2 without the original plaintext password. The recommended migration strategy involves gradual upgrading: when a user successfully authenticates (providing their plaintext password), verify against the existing bcrypt hash, then immediately re-hash the plaintext using Argon2 and store the new hash. Mark the user record as "upgraded" and remove the old bcrypt hash. This approach ensures zero user friction while progressively strengthening your credential database.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →