Argon2 Security-Focused API Documentation
Version: 1.0
Last Updated: 2025-07-05
**Security Classification: PUBLIC
Author: Phantom (phantom@metamui.id)
Overview
Argon2 is a memory-hard password hashing function winner of the Password Hashing Competition (2015). It provides strong resistance against GPU and ASIC attacks through high memory requirements. This documentation provides critical security guidance for the Argon2id variant implementation.
Security Level: Configurable (recommended ≥128 bits)
Salt Size: 16 bytes minimum (32 bytes recommended)
Output Size: Configurable (32 bytes recommended)
Security Warnings ⚠️
- Parameter Selection: NEVER use iterations < 3 or memory < 64 MB in production
- Salt Reuse: NEVER reuse salts - each password MUST have a unique salt
- Timing Attacks: Argon2 is NOT constant-time by design - use only for password storage
- Memory Requirements: Ensure system has 4x the memory parameter available
- Default Parameters: Our defaults (1 iteration, 64KB) are ONLY for testing!
API Functions
hash_password(password: str, salt: bytes, options: Argon2Options) -> str
Security Contract:
- Preconditions:
saltmust be at least 16 bytes (32 bytes recommended)saltmust be cryptographically random and uniqueoptions.memorymust be at least 65536 KB (64 MB) for productionoptions.iterationsmust be at least 3 for productionoptions.parallelismshould match CPU cores (typically 1-4)
- Postconditions:
- Returns encoded string with algorithm, version, parameters, salt, and hash
- Password is cleared from memory
- At least
options.memoryKB of RAM was used during computation
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | GPU Attack | ✅ | Memory-hard design prevents GPU acceleration | | ASIC Attack | ✅ | High memory requirements make ASICs impractical | | Rainbow Tables | ✅ | Unique salts prevent precomputation | | Timing Attack | ❌ | Deliberately variable-time for security | | Side Channel | ⚠️ | Memory access patterns may leak information |
Security Requirements:
- Production parameters MUST be significantly higher than defaults
- Each password MUST have a unique random salt
- Parameters must be tuned for target hardware (aim for 0.5-1 second)
Secure Usage Example:
# SECURE: Production-strength parameters
import secrets
# Generate cryptographically secure random salt
salt = secrets.token_bytes(32) # 32 bytes for maximum security
# Production parameters (tune for your hardware)
options = Argon2Options(
memory=262144, # 256 MB - prevents GPU attacks
iterations=4, # Multiple passes for security
parallelism=2, # Use available CPU cores
hash_length=32, # 256-bit output
variant='id' # Argon2id - best for passwords
)
# Hash the password
hashed = argon2.hash_password(password, salt, options)
# Clear password from memory immediately
secure_clear(password)
# Store the encoded result (includes all parameters)
user.password_hash = hashed # e.g., "$argon2id$v=19$m=262144,t=4,p=2$..."
Common Mistakes:
# INSECURE: Using default parameters in production
options = Argon2Options() # DEFAULTS: 1 iteration, 64KB - TOO WEAK!
hashed = argon2.hash_password(password, salt, options)
# INSECURE: Reusing salts
static_salt = b"myapplicationsalt" # NEVER DO THIS!
for password in passwords:
hashed = argon2.hash_password(password, static_salt, options) # VULNERABLE!
# INSECURE: Insufficient memory parameter
options = Argon2Options(
memory=1024, # Only 1 MB - GPU can crack this!
iterations=1, # Single pass - too fast!
parallelism=1
)
# INSECURE: Using timestamp as salt
salt = str(time.time()).encode() # PREDICTABLE - NOT RANDOM!
verify_password(password: str, encoded_hash: str) -> bool
Security Contract:
- Preconditions:
encoded_hashmust be valid Argon2 format stringencoded_hashshould be from trusted storage- System must have sufficient memory for parameters in hash
- Postconditions:
- Returns true only if password matches
- Password cleared from memory
- Execution time similar for success and failure
- No information leaked about mismatch reason
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Timing Attack | ⚠️ | Hash computation dominates timing | | Brute Force | ✅ | Rate limited by memory/computation requirements | | Error Oracle | ✅ | No information about failure reason | | Denial of Service | ⚠️ | High memory use - implement rate limiting |
Security Requirements:
- Implement rate limiting to prevent DoS attacks
- Monitor memory usage during verification
- Never reveal why verification failed
Secure Usage Example:
// SECURE: Verify with rate limiting and monitoring
use ratelimit::RateLimiter;
let limiter = RateLimiter::new(
3, // max attempts
Duration::from_secs(60) // per minute
);
pub fn verify_login(username: &str, password: &str) -> Result<User, AuthError> {
// Rate limit by username
if !limiter.check(username) {
log::warn!("Rate limit exceeded for user: {}", username);
return Err(AuthError::TooManyAttempts);
}
// Load user's password hash
let user = load_user(username)?;
// Verify password (this takes 0.5-1 second)
let valid = argon2::verify_password(password, &user.password_hash)?;
// Clear password from memory
secure_zero(password.as_bytes());
if valid {
limiter.reset(username);
Ok(user)
} else {
log::warn!("Failed login attempt for user: {}", username);
Err(AuthError::InvalidCredentials) // Generic error
}
}
Common Mistakes:
// INSECURE: No rate limiting
pub fn verify_login(username: &str, password: &str) -> Result<User, AuthError> {
let user = load_user(username)?;
if argon2::verify_password(password, &user.password_hash)? {
Ok(user)
} else {
Err(AuthError::InvalidPassword) // Reveals password was wrong
}
}
// INSECURE: Revealing timing information
let start = Instant::now();
let valid = argon2::verify_password(password, hash)?;
let duration = start.elapsed();
log::info!("Password check took {:?}", duration); // LEAKS INFORMATION!
// INSECURE: Not handling DoS potential
// Attacker can submit passwords with extreme parameters
let evil_hash = "$argon2id$v=19$m=4194304,t=100,p=32$..."; // 4GB RAM!
argon2::verify_password(password, evil_hash)?; // SYSTEM CRASH!
derive_key(password: str, salt: bytes, key_length: usize, options: Argon2Options) -> bytes
Security Contract:
- Preconditions:
saltmust be at least 16 bytes of random datakey_lengthtypically 32 or 64 bytesoptionsmust use production parameterspasswordshould have reasonable entropy
- Postconditions:
- Returns cryptographically secure key material
- Deterministic: same inputs produce same output
- Password cleared from memory
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Dictionary Attack | ✅ | High cost per attempt | | Parallel Attack | ✅ | Memory requirements limit parallelism | | Time-Memory Tradeoff | ✅ | Designed to resist TMTO attacks | | Weak Passwords | ⚠️ | Still vulnerable to common passwords |
Security Requirements:
- Use different salts for different purposes
- Never derive multiple keys with same salt
- Consider password strength requirements
Secure Usage Example:
// SECURE: Derive encryption key from password
async function deriveEncryptionKey(
password: string,
salt: Uint8Array
): Promise<CryptoKey> {
// Enforce password strength
if (!isPasswordStrong(password)) {
throw new Error('Password does not meet security requirements');
}
// Production parameters for key derivation
const options: Argon2Options = {
memory: 131072, // 128 MB
iterations: 4, // 4 passes
parallelism: 2, // 2 threads
hashLength: 32, // 256-bit key
variant: 'id'
};
// Derive key material
const keyMaterial = await argon2.deriveKey(
password,
salt,
32, // 256-bit key
options
);
// Clear password immediately
secureErase(password);
// Import as CryptoKey for WebCrypto API
return crypto.subtle.importKey(
'raw',
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false, // not extractable
['encrypt', 'decrypt']
);
}
// SECURE: Different salts for different purposes
const encryptionSalt = await generateSalt('encryption', userId);
const authSalt = await generateSalt('authentication', userId);
const encryptionKey = await deriveEncryptionKey(password, encryptionSalt);
const authKey = await deriveEncryptionKey(password, authSalt);
Common Mistakes:
// INSECURE: Using same salt for multiple purposes
const salt = generateSalt();
const encKey = argon2.deriveKey(password, salt, 32, options);
const authKey = argon2.deriveKey(password, salt, 32, options); // SAME SALT!
// INSECURE: Weak parameters for key derivation
const options = {
memory: 4096, // Only 4 MB - too weak!
iterations: 1, // Single iteration - too fast!
parallelism: 1
};
// INSECURE: Not clearing sensitive data
const key = argon2.deriveKey(password, salt, 32, options);
// password remains in memory - can be extracted!
// INSECURE: Deriving from low-entropy source
const pin = '1234'; // 4-digit PIN
const key = argon2.deriveKey(pin, salt, 32, strongOptions); // EASILY CRACKED!
Security Best Practices
Parameter Selection Guide
Choose parameters based on your security requirements and hardware:
| Use Case | Memory | Iterations | Time Target | Example |
|---|---|---|---|---|
| Web Login | 64 MB | 3 | 0.5s | m=65536,t=3,p=2 |
| High Security | 256 MB | 4 | 1s | m=262144,t=4,p=2 |
| Offline Storage | 1 GB | 6 | 3s | m=1048576,t=6,p=4 |
| Key Derivation | 128 MB | 4 | 1s | m=131072,t=4,p=2 |
Implementation Security
- Memory Protection: Clear all sensitive data after use
- Parameter Validation: Reject hashes with unsafe parameters
- Resource Limits: Set maximum memory to prevent DoS
- Rate Limiting: Implement per-user attempt limits
- Monitoring: Track failed attempts and resource usage
Common Integration Patterns
Secure Password Storage System
class SecurePasswordManager:
def __init__(self):
# Production parameters tuned for server hardware
self.options = Argon2Options(
memory=262144, # 256 MB
iterations=4, # 4 passes
parallelism=2, # 2 CPU cores
hash_length=32, # 256-bit hash
variant='id' # Argon2id
)
# Rate limiter: 5 attempts per 5 minutes
self.rate_limiter = RateLimiter(
max_attempts=5,
window_minutes=5
)
def hash_password(self, password: str) -> str:
"""Hash password with unique salt and production parameters."""
# Generate unique salt for this password
salt = secrets.token_bytes(32)
# Hash with production parameters
try:
hashed = argon2.hash_password(password, salt, self.options)
finally:
# Always clear password from memory
secure_clear(password)
return hashed
def verify_password(self, username: str, password: str, stored_hash: str) -> bool:
"""Verify password with rate limiting and monitoring."""
# Check rate limit
if not self.rate_limiter.allow(username):
logger.warning(f"Rate limit exceeded for user: {username}")
raise RateLimitError("Too many attempts")
# Validate stored hash format and parameters
if not self._validate_hash_params(stored_hash):
logger.error(f"Invalid hash parameters for user: {username}")
return False
# Verify password
try:
valid = argon2.verify_password(password, stored_hash)
except MemoryError:
logger.error("Insufficient memory for password verification")
return False
finally:
secure_clear(password)
# Log result
if valid:
self.rate_limiter.reset(username)
logger.info(f"Successful authentication for user: {username}")
else:
logger.warning(f"Failed authentication for user: {username}")
return valid
def _validate_hash_params(self, encoded_hash: str) -> bool:
"""Ensure hash parameters meet security minimums."""
try:
params = argon2.extract_parameters(encoded_hash)
return (
params.memory >= 65536 and # Min 64 MB
params.iterations >= 3 and # Min 3 iterations
params.salt_length >= 16 # Min 16 byte salt
)
except:
return False
Password Upgrade Strategy
/// Upgrade legacy passwords to Argon2
pub async fn upgrade_password_hash(
user_id: &str,
password: &str,
legacy_hash: &str,
hash_type: LegacyHashType,
) -> Result<String, Error> {
// First verify against legacy hash
let valid = match hash_type {
LegacyHashType::Bcrypt => bcrypt::verify(password, legacy_hash)?,
LegacyHashType::PBKDF2 => pbkdf2::verify(password, legacy_hash)?,
LegacyHashType::SHA256 => {
log::error!("Plain SHA256 passwords detected for user: {}", user_id);
sha256_verify(password, legacy_hash)? // Immediate upgrade required!
}
};
if !valid {
return Err(Error::InvalidCredentials);
}
// Generate new Argon2 hash with production parameters
let salt = secure_random(32);
let options = Argon2Options {
memory: 262144, // 256 MB
iterations: 4, // 4 passes
parallelism: 2, // 2 threads
hash_length: 32, // 256 bits
variant: Variant::Id,
};
let new_hash = argon2::hash_password(password, &salt, &options)?;
// Update user record atomically
update_user_password(user_id, &new_hash).await?;
log::info!("Password upgraded from {:?} to Argon2 for user: {}", hash_type, user_id);
Ok(new_hash)
}
Performance Considerations
| Configuration | Memory | Time | Security Notes |
|---|---|---|---|
| Low (Testing) | 64 MB | ~100ms | Only for development |
| Medium (Web) | 128 MB | ~500ms | Suitable for web apps |
| High (Standard) | 256 MB | ~1s | Recommended default |
| Very High | 512 MB | ~2s | For high-value targets |
| Extreme | 1 GB | ~4s | For offline key derivation |
Tuning Guidelines:
- Start with recommended parameters
- Measure on target hardware
- Adjust to meet time budget (0.5-1s for interactive)
- Ensure memory doesn’t cause swapping
- Test under concurrent load
Platform-Specific Security Notes
Python
- Uses
secretsmodule for salt generation - Memory clearing via
ctypes.memset - Consider
multiprocessingfor parallelism parameter - GIL may affect parallelism effectiveness
Rust
- Uses
getrandomfor salt generation - Automatic memory zeroing with
Zeroizetrait - Excellent parallelism support via
rayon - Stack allocated parameters cleared automatically
TypeScript/JavaScript
- Uses
crypto.getRandomValues()for salts - Manual memory clearing of typed arrays
- WebWorkers for parallelism in browsers
- Node.js worker_threads for server-side
Swift
- Uses
SecRandomCopyBytesfor salt generation - Careful with ARC retaining sensitive data
- Grand Central Dispatch for parallelism
- Consider iOS Keychain for hash storage
Kotlin
- Uses
SecureRandomfor salt generation - Manual array clearing required
- Coroutines for async operation
- Android Keystore for additional protection
WASM
- Limited parallelism support
- Memory growth may affect performance
- Ensure adequate memory limits
- Consider server-side hashing for security
Compliance and Standards
- Password Hashing Competition: Winner (2015)
- NIST SP 800-63B: Recommended for password storage
- OWASP: Preferred password hashing function
- RFC 9106: Argon2 Memory-Hard Function
- Common Criteria: Suitable for EAL4+ systems
Security Auditing
Verification Checklist
- All passwords use unique random salts (≥16 bytes)
- Production parameters meet minimums (≥64MB, ≥3 iterations)
- Rate limiting implemented for verification
- Resource limits prevent DoS attacks
- Password cleared from memory after use
- Failed attempts are logged and monitored
- Legacy hashes upgraded on successful login
- Parameter validation prevents weak hashes
Logging and Monitoring
# SECURE: Log security events without sensitive data
logger.info(f"Password changed for user_id={user_id}")
logger.warning(f"Multiple failed login attempts for user={username} ip={ip_address}")
logger.error(f"Potential DoS: High memory hash submitted from ip={ip_address}")
# INSECURE: Never log passwords or hashes
logger.debug(f"Password: {password}") # NEVER!
logger.debug(f"Hash: {password_hash}") # NEVER!
logger.debug(f"Salt: {salt}") # NEVER!
Common Security Mistakes to Avoid
- Using Argon2 for Non-Password Cryptography
- Argon2 is designed for passwords only
- Use HKDF or similar for key derivation from high-entropy secrets
- Storing Parameters Separately from Hash
- Always use encoded format that includes all parameters
- Never assume parameters - always parse from hash
- Using in Timing-Sensitive Operations
- Argon2 is deliberately slow and variable-time
- Never use for MAC, signatures, or session tokens
- Insufficient Memory on Production Systems
- Ensure system has 4x the memory parameter available
- Account for concurrent operations
- Not Monitoring Resource Usage
- Track memory and CPU usage
- Set alerts for unusual patterns
- Implement circuit breakers
Security Analysis
Threat Model: Argon2id Threat Model
The comprehensive threat analysis covers:
- Algorithm-specific attack vectors
- Implementation vulnerabilities
- Side-channel considerations
- Quantum resistance analysis (where applicable)
- Deployment recommendations
For complete security analysis and risk assessment, see the dedicated threat model documentation.
References
- RFC 9106 - Argon2 Memory-Hard Function
- Password Hashing Competition
- OWASP Password Storage Cheat Sheet
- Argon2 Security Audit Report
Support
Security Issues: security@metamui.id
Documentation Updates: phantom@metamui.id
Vulnerability Disclosure: See SECURITY.md
Document Version: 1.0
Review Cycle: Quarterly
Next Review: 2025-04-05
Classification: PUBLIC