Skip to content

Argon2

Argon2 is the winner of the Password Hashing Competition (2015) and is specified in RFC 9106. It’s designed specifically for secure password hashing and key derivation, with built-in resistance to GPU and ASIC attacks.

Overview

Argon2 provides strong protection against:

  • Brute-force attacks: Configurable memory and time costs
  • GPU/ASIC attacks: Memory-hard algorithm makes specialised hardware less effective
  • Side-channel attacks: Argon2id variant provides resistance to timing attacks
  • Rainbow tables: Built-in salt support

Key features:

  • RFC 9106 compliant: Standardised password hashing
  • Three variants: Argon2d, Argon2i, Argon2id
  • Tunable parameters: Adjust memory, iterations, and parallelism
  • Future-proof: Can increase difficulty as hardware improves

Variants

Argon2d

  • Optimised for: Maximum resistance to GPU/ASIC attacks
  • Use when: You need maximum security and side-channel attacks are not a concern
  • Memory access: Data-dependent (faster but potentially vulnerable to side-channels)

Argon2i

  • Optimised for: Resistance to side-channel attacks
  • Use when: Running in environments where timing attacks are possible
  • Memory access: Data-independent (slower but resistant to timing attacks)

Argon2id (Recommended)

  • Optimised for: Balance of both protections
  • Use when: General password hashing (most common use case)
  • Memory access: Hybrid approach (first half Argon2i, second half Argon2d)
  • Best choice: Recommended by RFC 9106 for password hashing

Basic Usage

Password Hashing (Argon2id)

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

int main() {
    // Password to hash
    std::string password = "MySecurePassword123!";

    // Generate random salt
    CryptoPP::AutoSeededRandomPool prng;
    CryptoPP::SecByteBlock salt(16);
    prng.GenerateBlock(salt, salt.size());

    // Output hash
    CryptoPP::SecByteBlock hash(32);

    // Argon2id parameters
    CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2ID);
    argon2.DeriveKey(
        hash, hash.size(),                    // Output
        (const CryptoPP::byte*)password.data(), password.size(),  // Password
        salt, salt.size(),                    // Salt
        2,                                    // Time cost (iterations)
        65536                                 // Memory cost (64 MB)
    );

    // Convert to hex for storage
    std::string hashHex, saltHex;
    CryptoPP::HexEncoder encoder;

    encoder.Attach(new CryptoPP::StringSink(hashHex));
    encoder.Put(hash, hash.size());
    encoder.MessageEnd();

    encoder.Attach(new CryptoPP::StringSink(saltHex));
    encoder.Put(salt, salt.size());
    encoder.MessageEnd();

    std::cout << "Salt: " << saltHex << std::endl;
    std::cout << "Hash: " << hashHex << std::endl;

    return 0;
}

Password Verification

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

bool verifyPassword(const std::string& password,
                   const std::string& saltHex,
                   const std::string& expectedHashHex) {
    // Decode salt and expected hash from hex
    CryptoPP::SecByteBlock salt(16);
    CryptoPP::SecByteBlock expectedHash(32);

    CryptoPP::HexDecoder decoder;
    decoder.Attach(new CryptoPP::ArraySink(salt, salt.size()));
    decoder.Put((const CryptoPP::byte*)saltHex.data(), saltHex.size());
    decoder.MessageEnd();

    decoder.Attach(new CryptoPP::ArraySink(expectedHash, expectedHash.size()));
    decoder.Put((const CryptoPP::byte*)expectedHashHex.data(), expectedHashHex.size());
    decoder.MessageEnd();

    // Compute hash with same parameters
    CryptoPP::SecByteBlock computedHash(32);
    CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2ID);

    argon2.DeriveKey(
        computedHash, computedHash.size(),
        (const CryptoPP::byte*)password.data(), password.size(),
        salt, salt.size(),
        2,      // Same time cost
        65536   // Same memory cost
    );

    // Constant-time comparison
    return CryptoPP::VerifyBufsEqual(
        computedHash, expectedHash, 32
    );
}

int main() {
    std::string password = "MySecurePassword123!";
    std::string salt = "A1B2C3D4E5F6...";  // From storage
    std::string hash = "9F8E7D6C5B4A...";  // From storage

    if (verifyPassword(password, salt, hash)) {
        std::cout << "Password verified!" << std::endl;
    } else {
        std::cout << "Invalid password" << std::endl;
    }

    return 0;
}

Using Argon2d

#include <cryptopp/argon2.h>

int main() {
    CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2D);  // Maximum GPU resistance

    // Same usage as Argon2id
    argon2.DeriveKey(/* parameters */);

    return 0;
}

Using Argon2i

#include <cryptopp/argon2.h>

int main() {
    CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2I);  // Side-channel resistant

    // Same usage as Argon2id
    argon2.DeriveKey(/* parameters */);

    return 0;
}

Parameter Selection

Memory Cost (m_cost)

Controls memory usage in KB:

  • Minimum: 8 KB (not recommended)
  • Low security: 32 MB (32768 KB)
  • Moderate security: 64 MB (65536 KB) - recommended minimum
  • High security: 256 MB (262144 KB)
  • Very high security: 1 GB (1048576 KB) or more
argon2.DeriveKey(
    hash, hash.size(),
    password, password.size(),
    salt, salt.size(),
    2,          // Time cost
    65536       // Memory cost: 64 MB
);

Time Cost (t_cost)

Number of iterations:

  • Minimum: 1 (not recommended)
  • Low security: 2 iterations - recommended minimum
  • Moderate security: 3-4 iterations
  • High security: 5+ iterations

More iterations = slower hashing = better security

Parallelism (lanes)

Number of parallel threads (advanced usage):

  • Default: 4 threads
  • Multi-core: 4-8 threads
  • Must divide memory cost evenly
// Advanced: using parallelism parameter
argon2.DeriveKey(
    hash, hash.size(),
    password, password.size(),
    salt, salt.size(),
    3,          // Time cost
    262144,     // Memory cost: 256 MB
    4           // Parallelism: 4 threads
);

Recommended Configurations

Web Applications (Moderate Security)

// Balance between security and user experience
// Target: ~500ms on server hardware
CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2ID);
argon2.DeriveKey(hash, 32, password, pwd_len, salt, 16,
                 2,      // 2 iterations
                 65536   // 64 MB memory
);

High Security Applications

// Stronger protection for sensitive data
// Target: 1-2 seconds
CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2ID);
argon2.DeriveKey(hash, 32, password, pwd_len, salt, 16,
                 4,       // 4 iterations
                 262144,  // 256 MB memory
                 4        // 4 threads
);

Maximum Security (Offline Storage)

// For protecting highly sensitive offline data
// Target: 5+ seconds
CryptoPP::Argon2 argon2(CryptoPP::Argon2::ARGON2ID);
argon2.DeriveKey(hash, 32, password, pwd_len, salt, 16,
                 8,        // 8 iterations
                 1048576,  // 1 GB memory
                 4         // 4 threads
);

Security Best Practices

Always Use Salt

// GOOD: Random salt per password
CryptoPP::AutoSeededRandomPool prng;
CryptoPP::SecByteBlock salt(16);  // 16 bytes minimum
prng.GenerateBlock(salt, salt.size());

// BAD: Never use fixed or empty salt
// CryptoPP::SecByteBlock salt;  // NO!

Store Salt with Hash

// Store both salt and hash together
struct PasswordEntry {
    std::string saltHex;
    std::string hashHex;
    int timeCost;
    int memoryCost;
};

Use Constant-Time Comparison

// GOOD: Prevents timing attacks
bool valid = CryptoPP::VerifyBufsEqual(hash1, hash2, 32);

// BAD: Vulnerable to timing attacks
// bool valid = (memcmp(hash1, hash2, 32) == 0);  // NO!

Choose Appropriate Parameters

  • Benchmark on your target hardware
  • Aim for at least 500ms hashing time
  • Prefer higher memory cost over more iterations
  • Use Argon2id unless you have specific requirements

Comparison with Other KDFs

FeatureArgon2idPBKDF2bcryptscrypt
RFC Standard✅ RFC 9106✅ RFC 2898✅ RFC 7914
Memory Hard
GPU Resistant⚠️
Side-channel Resistant⚠️⚠️⚠️
Tunable Memory
Modern Design✅ 2015❌ 2000❌ 1999⚠️ 2009

When to Use Argon2

Use Argon2id for:

  • User password hashing (primary use case)
  • API key derivation
  • Encryption key derivation from passwords
  • Any new password-based system

Use Argon2d when:

  • Maximum GPU resistance is critical
  • Running in a controlled environment (no side-channel risks)
  • Protecting offline data

Use Argon2i when:

  • Side-channel attacks are a serious concern
  • Running in untrusted environments
  • Processing untrusted input

Don’t use Argon2 for:

  • Fast hashing needs (use BLAKE3 or SHA-3)
  • HMAC (use HMAC-SHA256)
  • General key derivation where passwords aren’t involved (use HKDF)

API Reference

class Argon2 : public KeyDerivationFunction {
public:
    enum Variant { ARGON2D = 0, ARGON2I = 1, ARGON2ID = 2 };

    Argon2(Variant variant = ARGON2ID);

    size_t DeriveKey(
        byte* derived, size_t derivedLen,         // Output hash
        const byte* password, size_t passwordLen, // Password
        const byte* salt, size_t saltLen,         // Salt (16+ bytes)
        word32 timeCost = 3,                      // Iterations (2+ recommended)
        word32 memoryCost = 65536,                // Memory in KB (64 MB default)
        word32 parallelism = 4,                   // Threads (default 4)
        const byte* secret = nullptr, size_t secretLen = 0,       // Optional
        const byte* associatedData = nullptr, size_t adLen = 0    // Optional
    ) const;
};

Building with Argon2

Argon2 is included by default in cryptopp-modern 2025.11.0 and later.

Compiling Your Application

Include the header:

#include <cryptopp/argon2.h>

Compile and link:

# Linux/macOS
g++ -std=c++11 myapp.cpp -o myapp -lcryptopp

# Windows (MinGW)
g++ -std=c++11 myapp.cpp -o myapp.exe -lcryptopp

Further Reading