Argon2

Header: #include <cryptopp/argon2.h> | Namespace: CryptoPP Since: cryptopp-modern 2025.11.0
Thread Safety: Not thread-safe per instance; use separate instances per thread
Inherits from: KeyDerivationFunction

Memory-hard password hashing function designed to resist brute-force attacks from GPUs and ASICs. Argon2 won the Password Hashing Competition in 2015 and is standardized in RFC 9106.

Quick Example

#include <cryptopp/argon2.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <iostream>

int main() {
    using namespace CryptoPP;

    Argon2 argon2(Argon2::ARGON2ID);  // Recommended variant
    std::string password = "MySecurePassword123!";
    byte hash[32];

    // Generate random salt
    AutoSeededRandomPool rng;
    byte salt[16];
    rng.GenerateBlock(salt, sizeof(salt));

    // Hash password (t=3, m=64MB, p=4)
    argon2.DeriveKey(hash, sizeof(hash),
        (const byte*)password.data(), password.size(),
        salt, sizeof(salt),
        3, 65536, 4);  // RFC 9106 recommended params

    std::cout << "Password hashed successfully" << std::endl;
    return 0;
}

Overview

Argon2 is a password hashing function specifically designed to resist attacks from:

  • GPUs - High memory usage makes GPU attacks expensive
  • ASICs - Memory-hard design resists custom hardware
  • Side-channel attacks - Argon2i variant provides data-independent memory access
  • Time-memory trade-offs - Optimally hard to accelerate

Key features:

  • Memory-hard - Requires significant RAM, making parallel attacks expensive
  • Configurable - Adjust time cost, memory cost, and parallelism
  • Three variants - Argon2d, Argon2i, Argon2id for different threat models
  • Standardized - RFC 9106 (2021)
  • Winner - Password Hashing Competition (2015)

Usage Guidelines

ℹ️

Do:

  • Use Argon2id for password hashing (recommended by RFC 9106)
  • For general-purpose servers, a good default is: t=3, m=65536 (64 MiB), p=4 (RFC 9106 “low memory” profile)
  • Never go below roughly: t=2, m≈19 MiB, p=1 (OWASP absolute minimum)
  • Generate a unique random salt (≥16 bytes) for each password
  • Store salt alongside the hash (salt is not secret)
  • Verify passwords by re-hashing with the same salt and comparing
  • Increase parameters on more powerful servers (t=1, m=2097152 for 2 GiB)

Avoid:

  • Using Argon2 to derive keys from existing high-entropy secrets (use HKDF instead – Argon2 is for password-based key derivation)
  • Reusing salts across different passwords
  • Using predictable salts (timestamps, usernames, etc.)
  • Parameters below minimum: t < 1, m < 8*p, p < 1
  • Storing passwords as plain text or simple hashes (use Argon2!)

Variants

Argon2d (Data-Dependent)

Argon2 argon2(Argon2::ARGON2D);

When to use: Maximum resistance to GPU cracking attacks.

Properties:

  • Memory access depends on password (data-dependent)
  • Faster than Argon2i
  • Vulnerable to side-channel attacks on shared hardware

Best for: Cryptocurrency, offline hashing where side-channels aren’t a concern.


Argon2i (Data-Independent)

Argon2 argon2(Argon2::ARGON2I);

When to use: Protection against side-channel attacks.

Properties:

  • Memory access independent of password (constant-time-like)
  • Resistant to timing attacks and cache-timing attacks
  • Slightly slower than Argon2d

Best for: Shared servers, cloud environments, browsers.


Argon2id (Hybrid) ⭐ Recommended

Argon2 argon2(Argon2::ARGON2ID);  // Default

When to use: General password hashing (RFC 9106 recommendation).

Properties:

  • Uses Argon2i for first half pass, Argon2d for the rest
  • Balances GPU resistance with side-channel protection
  • Best of both worlds

Best for: Most applications, especially web servers and authentication systems.

Constants

  • defaultTimeCost = 3 - Default number of iterations
  • defaultMemoryCost = 65536 - Default memory in KiB (64 MiB)
  • defaultParallelism = 4 - Default number of threads/lanes

Constructor

Argon2(Variant variant = ARGON2ID)

Creates an Argon2 hasher with the specified variant.

Parameters:

  • variant - One of ARGON2D, ARGON2I, or ARGON2ID (default: ARGON2ID)

Exceptions: None

Example:

Argon2 argon2id;                      // Uses Argon2id (recommended)
Argon2 argon2d(Argon2::ARGON2D);      // Data-dependent variant
Argon2 argon2i(Argon2::ARGON2I);      // Data-independent variant

Public Methods

StaticAlgorithmName()

static std::string StaticAlgorithmName(Variant variant = ARGON2ID)

Returns the algorithm name for a variant.

Returns: "Argon2d", "Argon2i", or "Argon2id"

Thread Safety: Thread-safe (static method).


AlgorithmName()

std::string AlgorithmName() const

Returns the algorithm name for this instance’s variant.

Thread Safety: Thread-safe (const method).


MaxDerivedKeyLength()

size_t MaxDerivedKeyLength() const

Returns the maximum output size (SIZE_MAX).

Thread Safety: Thread-safe (const method).


GetValidDerivedLength()

size_t GetValidDerivedLength(size_t keylength) const

Validates and returns a valid output length.

Parameters:

  • keylength - Desired output length in bytes (minimum 4)

Returns: Valid key length

Exceptions: Throws InvalidDerivedKeyLength if less than 4 bytes


DeriveKey() - Simple Form

size_t DeriveKey(byte* derived, size_t derivedLen,
                 const byte* password, size_t passwordLen,
                 const byte* salt, size_t saltLen,
                 word32 timeCost = 3,
                 word32 memoryCost = 65536,
                 word32 parallelism = 4,
                 const byte* secret = nullptr,
                 size_t secretLen = 0,
                 const byte* associatedData = nullptr,
                 size_t associatedDataLen = 0) const

Derives a key from a password using Argon2.

Parameters:

  • derived - Output buffer for hash
  • derivedLen - Output size in bytes (minimum 4)
  • password - Password to hash
  • passwordLen - Password length in bytes
  • salt - Random salt (minimum 8 bytes, recommend 16+)
  • saltLen - Salt length in bytes
  • timeCost - Number of iterations (minimum 1, default 3)
  • memoryCost - Memory in KiB (minimum 8*parallelism, default 65536 = 64 MiB)
  • parallelism - Number of threads (minimum 1, default 4)
  • secret - Optional secret key
  • secretLen - Secret key length
  • associatedData - Optional associated data
  • associatedDataLen - Associated data length

Returns: Number of iterations performed (equals timeCost)

Exceptions:

  • InvalidDerivedKeyLength if derivedLen < 4
  • InvalidArgument if parameters are invalid (e.g., saltLen < 8, timeCost < 1, memoryCost < 8*parallelism)

Thread Safety: Not thread-safe.

Parameter Selection Guide

RFC 9106 Recommendations

First choice (2 GiB memory):

argon2.DeriveKey(hash, 32, password, passLen, salt, 16,
    1,       // t=1 (single pass)
    2097152, // m=2 GiB
    4);      // p=4 threads

Second choice (64 MiB memory - default):

argon2.DeriveKey(hash, 32, password, passLen, salt, 16,
    3,     // t=3 (three passes)
    65536, // m=64 MiB
    4);    // p=4 threads

Adjusting for Security/Performance

Higher security (if you have 8+ GB RAM):

// Very secure - takes ~2 seconds
argon2.DeriveKey(hash, 32, password, passLen, salt, 16,
    4,        // t=4 iterations
    4194304,  // m=4 GiB
    8);       // p=8 threads

Lower-cost configuration (OWASP minimum):

// Example of a lower-cost configuration for constrained systems.
// Use only if you can't afford the RFC 9106 / 64 MiB profiles.
argon2.DeriveKey(hash, 32, password, passLen, salt, 16,
    2,     // t=2 iterations
    19456, // m=19 MiB (≈ OWASP minimum)
    1);    // p=1 thread

Rule of thumb:

  • Time cost (t): Increase to slow down attacks (2-4 is typical)
  • Memory cost (m): Increase to resist GPU/ASIC attacks (64 MiB minimum, 2 GiB recommended)
  • Parallelism (p): Match available CPU cores (4 is typical, 1-8 range)

Complete Examples

Password Hashing and Verification

#include <cryptopp/argon2.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <iostream>
#include <string>

using namespace CryptoPP;

struct PasswordHash {
    std::string salt;  // Hex-encoded
    std::string hash;  // Hex-encoded
    word32 timeCost;
    word32 memoryCost;
    word32 parallelism;
};

PasswordHash HashPassword(const std::string& password) {
    Argon2 argon2(Argon2::ARGON2ID);

    // Generate random salt
    AutoSeededRandomPool rng;
    byte saltBytes[16];
    rng.GenerateBlock(saltBytes, sizeof(saltBytes));

    // Hash with recommended parameters
    byte hashBytes[32];
    argon2.DeriveKey(hashBytes, sizeof(hashBytes),
        (const byte*)password.data(), password.size(),
        saltBytes, sizeof(saltBytes),
        3, 65536, 4);  // t=3, m=64MB, p=4

    // Convert to hex for storage
    PasswordHash result;
    StringSource(saltBytes, sizeof(saltBytes), true,
        new HexEncoder(new StringSink(result.salt)));
    StringSource(hashBytes, sizeof(hashBytes), true,
        new HexEncoder(new StringSink(result.hash)));
    result.timeCost = 3;
    result.memoryCost = 65536;
    result.parallelism = 4;

    return result;
}

bool VerifyPassword(const std::string& password, const PasswordHash& stored) {
    Argon2 argon2(Argon2::ARGON2ID);

    // Decode stored salt
    std::string saltBytes;
    StringSource(stored.salt, true,
        new HexDecoder(new StringSink(saltBytes)));

    // Re-hash with same parameters
    byte hashBytes[32];
    argon2.DeriveKey(hashBytes, sizeof(hashBytes),
        (const byte*)password.data(), password.size(),
        (const byte*)saltBytes.data(), saltBytes.size(),
        stored.timeCost, stored.memoryCost, stored.parallelism);

    // Decode stored hash
    std::string storedHashBytes;
    StringSource(stored.hash, true,
        new HexDecoder(new StringSink(storedHashBytes)));

    // Constant-time comparison
    if (storedHashBytes.size() != sizeof(hashBytes)) {
        return false;
    }

    return VerifyBufsEqual(
        reinterpret_cast<const byte*>(storedHashBytes.data()),
        hashBytes,
        sizeof(hashBytes));
}

int main() {
    std::string password = "MySecurePassword123!";

    // Hash password
    PasswordHash hashed = HashPassword(password);
    std::cout << "Password hashed!" << std::endl;
    std::cout << "Salt: " << hashed.salt << std::endl;
    std::cout << "Hash: " << hashed.hash << std::endl;

    // Verify correct password
    if (VerifyPassword(password, hashed)) {
        std::cout << "✓ Password verified!" << std::endl;
    }

    // Verify wrong password
    if (!VerifyPassword("WrongPassword", hashed)) {
        std::cout << "✗ Wrong password rejected" << std::endl;
    }

    return 0;
}

With Server-Side Secret (Pepper)

// "Pepper" = fixed server-side secret, NOT stored in database
// Protects against database theft (attacker needs both DB + secret)
byte pepper[] = "ServerSecretKey2025";  // Store in config/env, not database

argon2.DeriveKey(hash, sizeof(hash),
    (const byte*)password.data(), password.size(),
    salt, sizeof(salt),          // Unique per user (stored in DB)
    3, 65536, 4,
    pepper, sizeof(pepper) - 1); // Same for all users (NOT in DB)

// Note: Salt = random, stored with hash. Pepper = fixed secret, stored separately.

Benchmarking Parameters

#include <chrono>

auto start = std::chrono::high_resolution_clock::now();

argon2.DeriveKey(hash, 32, password, passLen, salt, 16,
    t, m, p);

auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

std::cout << "Hashing took " << duration.count() << "ms" << std::endl;
// Aim for 500ms-1s for good security/UX balance

Performance

Argon2 is intentionally slow - that’s the point! It makes brute-force attacks expensive.

Typical timings (single-threaded):

  • t=3, m=64 MiB, p=1: ~200-300ms
  • t=3, m=64 MiB, p=4: ~50-100ms (parallelized)
  • t=1, m=2 GiB, p=4: ~500ms-1s

Scaling:

  • Time cost: Linear - doubling t doubles execution time
  • Memory cost: ~Linear - doubling m roughly doubles time
  • Parallelism: Improves performance up to available cores

Recommendation: Aim for 500ms-1s hashing time on your target hardware. This is fast enough for users but slow enough to resist attacks.

Security

  • Memory-hard - Resists GPU/ASIC attacks by requiring large RAM per guess
  • Time-memory trade-off resistant - Parameters are chosen so attackers can’t get big speedups by trading memory for time
  • Side-channel-aware - Argon2i and Argon2id use data-independent access patterns in their initial phase to mitigate cache/timing attacks, though implementations must still follow usual side-channel best practices
  • Standardised - Winner of the Password Hashing Competition; specified in RFC 9106 and subject to ongoing public cryptanalysis
  • No practical breaks - There are no practical attacks against Argon2id with RFC 9106–style parameters; published attacks mainly inform parameter and variant choices rather than completely breaking the function

Security level is entirely parameter-dependent:

  • For strong password hashing, follow RFC 9106 profiles:
    • Argon2id, t=1, m=2 GiB, p=4 (first recommended option)
    • Argon2id, t=3, m=64 MiB, p=4 (second option for memory-constrained systems)
  • Combine with online protections (rate limiting, lockout, 2FA). Argon2 raises the cost per guess; it doesn’t fix weak passwords by itself.

See Security Levels Explained for cryptographic security fundamentals.

Thread Safety

  • Per-instance: Not thread-safe. Do not use the same Argon2 instance from multiple threads simultaneously.
  • Multi-instance: Thread-safe. You can safely use different Argon2 instances in different threads.
  • Static methods: Thread-safe.
  • Parallelism parameter: Argon2 internally uses OpenMP for parallelization when available.

Example (safe):

void hashPasswords(const std::vector<std::string>& passwords) {
    #pragma omp parallel for
    for (size_t i = 0; i < passwords.size(); ++i) {
        Argon2 argon2(Argon2::ARGON2ID);  // Each thread has its own instance
        byte hash[32];
        // ... hash passwords[i] ...
    }
}

Comparison with Other Password Hashing Functions

FunctionMemory-HardGPU-ResistantSide-Channel SafeStatus
Argon2id✅ Yes✅ Yes✅ Yes⭐ Recommended
Argon2i✅ Yes✅ Yes✅✅ BestCloud/Shared
Argon2d✅ Yes✅✅ Best❌ NoOffline only
bcrypt⚠️ Moderate⚠️ Moderate✅ YesLegacy
scrypt✅ Yes✅ Yes⚠️ SomeAlternative
PBKDF2❌ No❌ No✅ YesAvoid

Why Argon2 over bcrypt/PBKDF2:

  • Memory-hard - bcrypt uses little memory, PBKDF2 uses none
  • Configurable - Adjust parameters as hardware improves
  • Standardized - RFC 9106, winner of PHC
  • Modern - Designed with current attack vectors in mind

Use Cases

  • Password hashing - Primary use case for authentication
  • Cryptocurrency wallets - Protecting private keys with passwords
  • Disk encryption - Key derivation from user passwords
  • Secure storage - Protecting sensitive data with password-derived keys
  • Key derivation from secrets - Use HKDF instead (Argon2 is for passwords)
  • Fast hashing - Use BLAKE3 for general-purpose hashing

Test Vectors

Use these to verify your Argon2 implementation (from RFC 9106 Section 6.5):

VariantPasswordSalttm (KiB)pOutput (32 bytes, hex)
Argon2d0x0101...01 (32 bytes)0x0202...02 (16 bytes)3324512b391b6f1162975...
Argon2i0x0101...01 (32 bytes)0x0202...02 (16 bytes)3324c814d9d1dc7f37aa1...
Argon2id0x0101...01 (32 bytes)0x0202...02 (16 bytes)33240d640df58d78766c0...

Note: The full test vectors with complete inputs/outputs are in RFC 9106 Section 6.5. The values above use m=32 KiB for quick validation; production parameters should be much higher.

See Also