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>
#include <string>
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 - Designed to make attacks using reduced memory more expensive
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 data-independent addressing for the first half of the first pass, then data-dependent addressing
- Balances GPU resistance with side-channel protection
- Best of both worlds
Best for: Most applications, especially web servers and authentication systems.
Legacy variants
Argon2 argon2i_legacy(Argon2::ARGON2I_LEGACY);
Argon2 argon2id_legacy(Argon2::ARGON2ID_LEGACY);Since: cryptopp-modern 2026.10.0
ARGON2I_LEGACY and ARGON2ID_LEGACY reproduce the Argon2i and Argon2id output of cryptopp-modern 2025.11.0 through 2026.9.1, which differed from RFC 9106 for affected parameter sets. They exist so hashes and keys from those releases can be verified or recovered and then rehashed with the standard variant. Do not use them for new hashes or keys. See Migrating Argon2 Hashes from Earlier Releases.
Constants
defaultTimeCost = 3- Default number of iterationsdefaultMemoryCost = 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 ofARGON2D,ARGON2I,ARGON2ID(default), or the migration-onlyARGON2I_LEGACYandARGON2ID_LEGACY
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", "Argon2id", "Argon2i-legacy", or "Argon2id-legacy"
Thread Safety: Thread-safe (static method).
AlgorithmName()
std::string AlgorithmName() constReturns the algorithm name for this instance’s variant.
Thread Safety: Thread-safe (const method).
MaxDerivedKeyLength()
size_t MaxDerivedKeyLength() constReturns the maximum output size (SIZE_MAX).
Thread Safety: Thread-safe (const method).
GetValidDerivedLength()
size_t GetValidDerivedLength(size_t keylength) constValidates 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) constDerives a key from a password using Argon2.
Parameters:
derived- Output buffer for hashderivedLen- Output size in bytes (minimum 4)password- Password to hashpasswordLen- Password length in bytessalt- Random salt (minimum 8 bytes, recommend 16+)saltLen- Salt length in bytestimeCost- 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 keysecretLen- Secret key lengthassociatedData- Optional associated dataassociatedDataLen- Associated data length
Returns: Number of iterations performed (equals timeCost)
Exceptions:
InvalidDerivedKeyLengthifderivedLen < 4InvalidArgumentif 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):
// Higher-cost configuration
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 <cryptopp/filters.h>
#include <cryptopp/misc.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.
Illustrative timings (hardware and implementation dependent):
- 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 uses data-independent memory access throughout; Argon2id uses it for the first half of the first pass, then data-dependent access. This mitigates cache and timing attacks on the independent portion, 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 attack is currently known against Argon2id with recommended 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
Argon2instance from multiple threads simultaneously. - Multi-instance: Thread-safe. You can safely use different
Argon2instances 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
| Function | Memory-Hard | GPU-Resistant | Data-dependent memory access | Status |
|---|---|---|---|---|
| Argon2id | ✅ Yes | ✅ Yes | After the first half of the first pass | ⭐ Recommended |
| Argon2i | ✅ Yes | ✅ Yes | None | Cloud/Shared |
| Argon2d | ✅ Yes | ✅✅ Best | Throughout | Offline only |
| bcrypt | ⚠️ Moderate | ⚠️ Moderate | Yes (key-dependent S-box lookups) | Legacy |
| scrypt | ✅ Yes | ✅ Yes | Yes | Alternative |
| PBKDF2 | ❌ No | ❌ No | None | Avoid |
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
The RFC 9106 Section 6.5 vectors, which the library runs in its default test suite. All three use password 0x01 repeated 32 times, salt 0x02 repeated 16 times, secret 0x03 repeated 8 times, associated data 0x04 repeated 12 times, t=3, m=32 KiB, p=4 and a 32-byte output. The secret and associated data must be supplied through the optional DeriveKey parameters to reproduce them.
| Variant | Output (32 bytes, hex) |
|---|---|
| Argon2d | 512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb |
| Argon2i | c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8 |
| Argon2id | 0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659 |
The full inputs are in RFC 9106 Section 6.5. m=32 KiB is for validation only; production parameters should be much higher.
See Also
- Argon2 Guide - Detailed guide with more examples
- Password Hashing Guide - Best practices
- Migrating Argon2 Hashes from Earlier Releases - Legacy variants and rehashing
- Security Concepts - Understanding security
- BLAKE3 - Don’t use this for passwords!
- HKDF - For key derivation from secrets
- RFC 9106 - Official Argon2 specification
- Password Hashing Competition - Background