DevToolBoxKOSTENLOS
Blog

bcrypt vs Argon2 vs scrypt: Passwort-Hashing 2026

10 Min. Lesezeitvon DevToolBox

Choosing the right password hashing algorithm is one of the most critical security decisions you will make in any application that handles user credentials. A weak choice can expose millions of passwords in a data breach, while the right algorithm can make brute-force attacks economically infeasible. This comprehensive guide compares the three leading password hashing algorithms — Bcrypt, Scrypt, and Argon2 — so you can make an informed decision for your project.

Why Password Hashing Matters

Password hashing is a one-way transformation that converts a plaintext password into a fixed-length string of characters. Unlike encryption, hashing is irreversible by design — you cannot recover the original password from its hash. When a user logs in, the system hashes the submitted password and compares it to the stored hash.

Storing passwords in plaintext or using fast general-purpose hash functions like MD5 or SHA-256 is dangerously insecure. Modern GPUs can compute billions of SHA-256 hashes per second, meaning an attacker with a stolen database can crack most passwords in hours or days.

Purpose-built password hashing algorithms solve this by being intentionally slow and resource-intensive. They incorporate three key defenses:

  • Salt: Salting: A random value added to each password before hashing, ensuring identical passwords produce different hashes.
  • Cost: Cost factor: A configurable work parameter that controls how slow the hash computation is, allowing you to increase difficulty as hardware improves.
  • Memory: Memory hardness: Some algorithms require large amounts of RAM, making attacks with specialized hardware (GPUs, ASICs, FPGAs) far more expensive.
# Comparison: General-purpose vs Password hashing speed
SHA-256:    ~10,000,000,000 hashes/sec  (GPU)  ← Dangerously fast
MD5:        ~25,000,000,000 hashes/sec  (GPU)  ← Even worse
Bcrypt:     ~       50,000 hashes/sec  (GPU)  ← 200,000x slower
Argon2id:   ~        1,000 hashes/sec  (GPU)  ← 10,000,000x slower (with 19MB memory)

Bcrypt: The Battle-Tested Standard

How Bcrypt Works

Bcrypt was designed in 1999 by Niels Provos and David Mazieres, based on the Blowfish cipher. It uses a cost factor (work factor) that determines the number of iterations: the hash computation runs 2^cost rounds of the expensive key setup phase. The default cost factor is 10 (1,024 rounds), but modern recommendations suggest 12-14 (4,096-16,384 rounds).

A bcrypt hash string contains everything needed for verification:

$2b$12$LJ3m4ys3Lg2VBe8EWdKFTe8x7N3fWs0YhW1qDZGhIJFG3rJ1/Bxiy

 ││  ││ └────────────────────── Hash (31 chars, Base64)
 ││  │└──────────────────────── Salt (22 chars, Base64, 128-bit)
 ││  └───────────────────────── Cost factor (12 = 2^12 = 4,096 rounds)
 │└──────────────────────────── Version (2b = current)
 └───────────────────────────── Algorithm identifier ($2b$ = bcrypt)

Bcrypt Pros

  • Extremely well-tested: 25+ years of cryptographic scrutiny with no practical attacks found.
  • Built-in salt: Automatically generates and stores a 128-bit random salt with each hash.
  • Adaptive cost: The work factor can be increased over time as hardware gets faster.
  • Universal library support: Available in virtually every programming language and framework.
  • Simple API: Easy to use correctly, reducing the chance of implementation mistakes.

Bcrypt Cons

  • CPU-only hardness: Bcrypt is not memory-hard, making it vulnerable to GPU and ASIC-based attacks (though still expensive).
  • 72-byte password limit: Bcrypt truncates passwords longer than 72 bytes. This rarely matters in practice but is a design limitation.
  • Fixed memory usage: Uses only 4KB of RAM regardless of cost factor, which does not increase resistance to parallel attacks.
  • No parallelism parameter: Cannot leverage multi-core CPUs for legitimate hashing while remaining single-threaded for attackers.

When to Use Bcrypt

Bcrypt is an excellent default choice for most applications. Use it when you need a proven, widely-supported algorithm and your threat model does not require memory hardness. It is the safest "I do not want to think about this too much" choice.

Scrypt: Memory-Hard Pioneer

How Scrypt Works

Scrypt was designed in 2009 by Colin Percival for the Tarsnap online backup service. Its key innovation is memory hardness: the algorithm requires a large amount of RAM proportional to its difficulty setting. This makes parallel attacks on GPUs, ASICs, and FPGAs significantly more expensive because each parallel instance needs its own block of memory.

Scrypt has three configurable parameters:

  • N: N (CPU/memory cost): Determines the memory and CPU cost. Must be a power of 2. Higher N means more memory and time. Common values: 2^14 (16,384) to 2^20 (1,048,576).
  • r: r (block size): Controls the sequential memory read size. Typical value: 8. Increasing r increases memory usage linearly.
  • p: p (parallelism): The number of parallel chains. Typical value: 1. Increasing p allows parallelizing computation on the defender side.
# Scrypt memory calculation
Memory = 128 * N * r bytes

N = 2^14 (16384), r = 8:
  128 * 16384 * 8 = 16,777,216 bytes = 16 MB per hash

N = 2^15 (32768), r = 8:
  128 * 32768 * 8 = 33,554,432 bytes = 32 MB per hash

N = 2^20 (1048576), r = 8:
  128 * 1048576 * 8 = 1,073,741,824 bytes = 1 GB per hash

Memory usage formula: 128 * N * r bytes. With N=2^14 and r=8, that is 128 * 16384 * 8 = 16 MB per hash.

Scrypt Pros

  • Memory hardness: Requires significant RAM per hash, making GPU/ASIC attacks much more expensive.
  • Configurable parameters: Separate control over CPU cost, memory cost, and parallelism.
  • Proven in production: Used by Litecoin, Dogecoin, and many cryptocurrency systems since 2011.
  • Good security margin: No practical attacks found in 15+ years.

Scrypt Cons

  • Complex parameter tuning: Three interdependent parameters (N, r, p) make it harder to configure correctly than bcrypt.
  • Time-memory tradeoff vulnerability: An attacker can reduce memory requirements by spending more CPU time, partially defeating the memory-hardness guarantee.
  • Fewer built-in libraries: Not as universally supported as bcrypt, especially in older frameworks.
  • No side-channel resistance: Not specifically designed to resist cache-timing attacks.

When to Use Scrypt

Use scrypt when you need memory hardness and Argon2 is not available in your environment. It is a solid choice for applications where you want to make GPU-based cracking significantly more expensive.

Argon2 (Argon2id): The Modern Gold Standard

How Argon2 Works

Argon2 won the Password Hashing Competition (PHC) in 2015, a multi-year open competition to find the best password hashing algorithm. It was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich. Argon2 comes in three variants:

Argon2d: Maximizes resistance to GPU cracking attacks. Data-dependent memory access makes it vulnerable to side-channel attacks.
Argon2i: Optimized for resistance to side-channel attacks. Uses data-independent memory access. Better for environments where timing attacks are a concern.
Argon2id: A hybrid that uses Argon2i for the first pass (side-channel resistance) and Argon2d for subsequent passes (GPU resistance). This is the recommended variant for password hashing.

Argon2 has three main parameters:

  • m: m (memory cost): The amount of memory in KiB. OWASP recommends at least 19 MiB (19456 KiB) for Argon2id. Higher is better.
  • t: t (time cost / iterations): The number of passes over memory. Minimum 2 for Argon2id. More iterations = slower hashing.
  • p: p (parallelism): The number of threads. Set to the number of CPU cores available for hashing. Typical value: 1-4.
# Argon2id hash format
$argon2id$v=19$m=19456,t=2,p=1$c2FsdHNhbHRzYWx0$W1BLEn4OWxCjU2F...

 ││        ││  ││       ││     ││                   └── Hash (Base64)
 ││        ││  ││       ││     │└── Salt (Base64)
 ││        ││  ││       ││     └──── p=1 (parallelism)
 ││        ││  ││       │└────────── t=2 (iterations)
 ││        ││  ││       └─────────── m=19456 (memory in KiB = 19 MiB)
 ││        ││  │└─────────────────── v=19 (version 1.3)
 ││        │└──┘──────────────────── argon2id (variant)
 └┘────────┘──────────────────────── Algorithm identifier

Argon2 Pros

  • PHC winner: Rigorously evaluated by the global cryptographic community in a multi-year competition.
  • Superior memory hardness: No known time-memory tradeoff attacks (unlike scrypt).
  • Side-channel resistance: Argon2id hybrid mode provides both GPU resistance and side-channel resistance.
  • Three independent parameters: Fine-grained control over memory, time, and parallelism.
  • Modern design: Incorporates lessons learned from bcrypt and scrypt shortcomings.
  • OWASP recommended: The primary recommendation in the OWASP Password Storage Cheat Sheet.

Argon2 Cons

  • Younger algorithm: Only since 2015, so it has fewer years of real-world deployment than bcrypt (1999).
  • Library availability: Not as universally supported as bcrypt in all languages and frameworks, though adoption is growing rapidly.
  • Parameter complexity: Like scrypt, tuning three parameters requires some understanding of your deployment environment.
  • Memory allocation overhead: Allocating and freeing large blocks of memory per hash can impact performance in high-throughput scenarios.

When to Use Argon2

Argon2id is the recommended choice for new applications if your language or framework has a mature library. It provides the best overall security properties. If you are building anything that stores passwords today, Argon2id should be your first choice.

Side-by-Side Comparison

FeatureBcryptScryptArgon2id
Year Introduced199920092015
Memory HardnessNone (4KB fixed)Yes (configurable)Yes (configurable)
Side-Channel ResistanceN/ANoYes (hybrid mode)
GPU/ASIC ResistanceModerateHighHighest
Configurable Parameters1 (cost factor)3 (N, r, p)3 (m, t, p)
Max Password Length72 bytesUnlimitedUnlimited
Library AdoptionUniversalGoodGrowing rapidly
OWASP RecommendationAcceptable (2nd choice)Acceptable (3rd choice)Primary recommendation
Configuration ComplexityVery LowMediumMedium
Hashing Speed (default)~300ms (cost=12)~100ms (N=2^14)~200ms (19MiB, t=2)

Code Examples

Node.js Examples

// ============================================
// Bcrypt (Node.js) - using 'bcrypt' package
// ============================================
const bcrypt = require('bcrypt');

// Hash a password
const saltRounds = 12; // Cost factor: 2^12 = 4,096 iterations
const password = 'mySecurePassword123';

// Async (recommended)
const hash = await bcrypt.hash(password, saltRounds);
// => "$2b$12$LJ3m4ys3Lg2VBe8EWdKFTe..."

// Verify a password
const isMatch = await bcrypt.compare(password, hash);
// => true

// ============================================
// Scrypt (Node.js) - built-in crypto module
// ============================================
const crypto = require('crypto');

// Hash a password
const salt = crypto.randomBytes(16);
const N = 32768;  // CPU/memory cost (2^15)
const r = 8;      // Block size
const p = 1;      // Parallelism
const keyLen = 64; // Output length in bytes

const scryptHash = crypto.scryptSync(password, salt, keyLen, { N, r, p });

// Store both salt and hash (e.g., as hex strings)
const stored = salt.toString('hex') + ':' + scryptHash.toString('hex');

// Verify: split stored value, re-derive, and compare
const [storedSalt, storedHash] = stored.split(':');
const derivedHash = crypto.scryptSync(
  password,
  Buffer.from(storedSalt, 'hex'),
  keyLen,
  { N, r, p }
);
const isValid = crypto.timingSafeEqual(
  Buffer.from(storedHash, 'hex'),
  derivedHash
);

// ============================================
// Argon2 (Node.js) - using 'argon2' package
// ============================================
const argon2 = require('argon2');

// Hash a password (Argon2id is the default)
const argon2Hash = await argon2.hash(password, {
  type: argon2.argon2id,   // Use Argon2id variant
  memoryCost: 19456,        // 19 MiB (OWASP minimum)
  timeCost: 2,              // 2 iterations
  parallelism: 1,           // 1 thread
});
// => "$argon2id$v=19$m=19456,t=2,p=1$..."

// Verify a password
const isArgon2Match = await argon2.verify(argon2Hash, password);
// => true

// Check if hash needs rehashing (e.g., after config change)
const needsRehash = argon2.needsRehash(argon2Hash, {
  memoryCost: 19456,
  timeCost: 2,
  parallelism: 1,
});

Python Examples

# ============================================
# Bcrypt (Python) - using 'bcrypt' package
# ============================================
import bcrypt

password = b"mySecurePassword123"

# Hash a password
salt = bcrypt.gensalt(rounds=12)  # Cost factor 12
hashed = bcrypt.hashpw(password, salt)
# => b"$2b$12$LJ3m4ys3Lg2VBe8EWdKFTe..."

# Verify a password
is_valid = bcrypt.checkpw(password, hashed)
# => True

# ============================================
# Scrypt (Python) - using hashlib (built-in)
# ============================================
import hashlib
import os
import hmac

password = b"mySecurePassword123"
salt = os.urandom(16)

# Hash a password
scrypt_hash = hashlib.scrypt(
    password,
    salt=salt,
    n=32768,     # CPU/memory cost (2^15)
    r=8,         # Block size
    p=1,         # Parallelism
    dklen=64     # Output length
)

# Store salt + hash together
stored = salt.hex() + ":" + scrypt_hash.hex()

# Verify: re-derive and use constant-time comparison
stored_salt, stored_hash = stored.split(":")
derived = hashlib.scrypt(
    password,
    salt=bytes.fromhex(stored_salt),
    n=32768, r=8, p=1, dklen=64
)
is_valid = hmac.compare_digest(
    bytes.fromhex(stored_hash), derived
)

# ============================================
# Argon2 (Python) - using 'argon2-cffi' package
# ============================================
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=2,        # 2 iterations
    memory_cost=19456,  # 19 MiB
    parallelism=1,      # 1 thread
    hash_len=32,        # Output hash length
    salt_len=16,        # Salt length
)

# Hash a password
argon2_hash = ph.hash("mySecurePassword123")
# => "$argon2id$v=19$m=19456,t=2,p=1$..."

# Verify a password
try:
    ph.verify(argon2_hash, "mySecurePassword123")
    print("Password is correct")
except Exception:
    print("Password is incorrect")

# Check if hash needs rehashing
if ph.check_needs_rehash(argon2_hash):
    # Re-hash with current parameters
    new_hash = ph.hash("mySecurePassword123")
    # Update stored hash in database

Recommendation Decision Guide

Use this decision guide to choose the right algorithm for your project:

┌─────────────────────────────────────────────────────────────┐
│         Password Hashing Algorithm Decision Guide           │
└─────────────────────────────┬───────────────────────────────┘
                              │
                              ▼
               ┌──────────────────────────────┐
               │  Is Argon2id available with   │
               │  a mature library in your     │
               │  language / framework?         │
               └──────┬────────────┬───────────┘
                      │            │
                    YES            NO
                      │            │
                      ▼            ▼
         ┌─────────────────┐   ┌──────────────────────────┐
         │  Use Argon2id    │   │  Do you need memory       │
         │  m=19456 (19MB)  │   │  hardness for GPU/ASIC    │
         │  t=2, p=1        │   │  resistance?              │
         └─────────────────┘   └──────┬──────────┬─────────┘
                                      │          │
                                    YES          NO
                                      │          │
                                      ▼          ▼
                          ┌───────────────┐  ┌─────────────────┐
                          │  Use Scrypt    │  │  Use Bcrypt      │
                          │  N=2^15, r=8   │  │  cost factor=12+ │
                          │  p=1           │  │                  │
                          └───────────────┘  └─────────────────┘
Q: Is Argon2id available in your language/framework with a mature, well-maintained library?

YES: Use Argon2id with OWASP-recommended settings (m=19456, t=2, p=1).

NO: Continue to next question.

Q: Do you need memory hardness to defend against GPU/ASIC attacks?

YES: Use Scrypt with N=2^15, r=8, p=1 (or higher N if your server can handle it).

NO: Use Bcrypt with a cost factor of 12 or higher.

Q: Are you migrating from an older algorithm (MD5, SHA-1, SHA-256)?

Do not force all users to reset passwords. Instead, wrap the old hash: Argon2id(existing_sha256_hash). On next login, re-hash with Argon2id directly.

Summary: For new projects in 2024+, use Argon2id. For existing projects using bcrypt, there is no urgent need to migrate — bcrypt is still secure. If you are choosing between bcrypt and scrypt, bcrypt is simpler and equally safe for most threat models.

OWASP Recommended Settings

The OWASP Password Storage Cheat Sheet provides these minimum recommended configurations:

AlgorithmRecommended ConfigurationNotes
Argon2idm=19456 (19 MiB), t=2, p=1Primary recommendation. Increase m if server RAM allows.
Bcryptcost=12 (minimum 10)Second choice. Increase cost factor to target 200-500ms.
ScryptN=2^17 (131072), r=8, p=1Third choice. Memory = 128 * N * r = 128 MB per hash.

Migrating Between Algorithms

If you need to migrate from one hashing algorithm to another without forcing password resets, use the "wrap and upgrade" strategy:

Step 1: Wrap existing hashes with the new algorithm. For example, if you are migrating from bcrypt to Argon2id, store Argon2id(bcrypt_hash).
Step 2: When a user logs in, verify the password against the wrapped hash.
Step 3: After successful verification, re-hash the plaintext password directly with Argon2id and update the stored hash.
Step 4: Add a version field to your user table to track which hashing scheme each user is on.
// Migration example: bcrypt → Argon2id (Node.js)
const bcrypt = require('bcrypt');
const argon2 = require('argon2');

async function verifyAndUpgrade(password, storedHash, userId) {
  // Check hash version
  if (storedHash.startsWith('$argon2id$')) {
    // Already using Argon2id — verify directly
    return await argon2.verify(storedHash, password);
  }

  if (storedHash.startsWith('$2b$')) {
    // Still using bcrypt — verify with bcrypt
    const isValid = await bcrypt.compare(password, storedHash);

    if (isValid) {
      // Upgrade: re-hash directly with Argon2id
      const newHash = await argon2.hash(password, {
        type: argon2.argon2id,
        memoryCost: 19456,
        timeCost: 2,
        parallelism: 1,
      });

      // Update database
      await db.query(
        'UPDATE users SET password_hash = $1, hash_version = $2 WHERE id = $3',
        [newHash, 'argon2id', userId]
      );
    }

    return isValid;
  }

  throw new Error('Unknown hash format');
}

Important: Important: Never store plaintext passwords, even temporarily during migration. The wrapping approach ensures all hashes are always protected by at least one strong algorithm.

Common Implementation Pitfalls

Using a Global Salt

A global salt (same salt for all passwords) defeats the purpose. If one password is cracked, all identical passwords are immediately exposed. All three algorithms (bcrypt, scrypt, Argon2) generate unique random salts per hash by default — do not override this behavior.

Cost Factor Too Low

Using the minimum cost factor makes hashing too fast. Aim for 200-500ms per hash on your production hardware. Benchmark on your actual server and increase the cost factor until you reach the target latency.

Pepper Without Proper Key Management

A pepper (server-side secret added to passwords before hashing) can add defense-in-depth, but only if stored securely outside the database (e.g., in a hardware security module or environment variable). If the pepper is compromised alongside the database, it provides no benefit.

Not Rehashing on Login

As hardware improves, you should periodically increase the cost factor. When a user logs in successfully, check if their hash uses an outdated cost factor and re-hash with the current settings.

Timing Attacks on Comparison

Always use constant-time comparison functions when verifying hashes. All reputable password hashing libraries include this, but if you are comparing hashes manually, use a dedicated constant-time comparison function.

Performance Benchmarks

These benchmarks were measured on a standard 4-core server CPU (Intel Xeon E-2236, 3.4GHz) with 16GB RAM. Actual performance will vary by hardware.

Algorithm & ConfigTime per HashMemory per HashAttacker Rate (RTX 4090)
Bcrypt (cost=10)~75ms4 KB~150K hashes/sec
Bcrypt (cost=12)~300ms4 KB~38K hashes/sec
Bcrypt (cost=14)~1.2s4 KB~9.5K hashes/sec
Scrypt (N=2^14, r=8)~100ms16 MB~5K hashes/sec
Scrypt (N=2^15, r=8)~200ms32 MB~2.5K hashes/sec
Scrypt (N=2^17, r=8)~800ms128 MB~300 hashes/sec
Argon2id (19MiB, t=2)~200ms19 MB~1K hashes/sec
Argon2id (64MiB, t=3)~800ms64 MB~150 hashes/sec
Argon2id (256MiB, t=4)~3.5s256 MB~20 hashes/sec

Frequently Asked Questions

Is bcrypt still safe to use in 2024?

Yes, bcrypt is still considered safe with a cost factor of 12 or higher. No practical attacks have been found against bcrypt in over 25 years. However, for new projects, Argon2id is the preferred choice because it provides additional defense against GPU-based attacks through memory hardness.

What happens if I use SHA-256 for password hashing?

SHA-256 is a general-purpose hash function designed for speed, not for password hashing. A modern GPU can compute over 10 billion SHA-256 hashes per second. This means an attacker can brute-force most passwords in hours. Always use a purpose-built password hashing algorithm (bcrypt, scrypt, or Argon2).

How do I choose the right cost factor / work parameters?

The goal is to make each hash take 200-500 milliseconds on your production server. Start with the recommended defaults and benchmark on your actual hardware. Increase parameters until you reach the target latency. Remember that login endpoints typically do not need to handle thousands of requests per second, so you can afford more expensive hashing.

Can I use Argon2 for anything other than passwords?

Yes, Argon2 can be used for key derivation (deriving encryption keys from passwords), proof-of-work systems, and cryptocurrency mining. The Argon2d variant is specifically designed for use cases where side-channel attacks are not a concern, such as cryptocurrency mining.

Should I add a pepper in addition to a salt?

A pepper (a secret key stored separately from the database) can provide defense-in-depth. If an attacker obtains only the database but not the application server or key management system, the pepper prevents offline cracking. However, it adds complexity and must be managed carefully (key rotation, secure storage). For most applications, a well-configured Argon2id hash with a strong cost factor is sufficient.

How often should I increase the cost factor?

Review your password hashing parameters annually or whenever you upgrade server hardware. Moore's Law suggests doubling CPU power roughly every 18 months, which means you should increase your cost factor by 1 (doubling the work) approximately every 18-24 months. Implement "hash upgrading" — re-hash passwords with the new cost factor when users log in successfully.

𝕏 Twitterin LinkedIn
War das hilfreich?

Bleiben Sie informiert

Wöchentliche Dev-Tipps und neue Tools.

Kein Spam. Jederzeit abbestellbar.

Verwandte Tools ausprobieren

🔒Bcrypt Hash Generator#Hash Generator🔐HMAC Generator🔑Password Generator

Verwandte Artikel

Wie JWT funktioniert: Vollständiger Guide zu JSON Web Tokens

Verstehen Sie JWT-Authentifizierung, Header, Payload und Signatur.

Passwort-Stärke-Anforderungen 2025: NIST-Richtlinien & Best Practices

Moderne Passwort-Anforderungen basierend auf NIST SP 800-63B. Mindestlänge, Komplexitätsregeln, Blocklisten und MFA.