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 ⚠️

  1. Parameter Selection: NEVER use iterations < 3 or memory < 64 MB in production
  2. Salt Reuse: NEVER reuse salts - each password MUST have a unique salt
  3. Timing Attacks: Argon2 is NOT constant-time by design - use only for password storage
  4. Memory Requirements: Ensure system has 4x the memory parameter available
  5. Default Parameters: Our defaults (1 iteration, 64KB) are ONLY for testing!

API Functions

hash_password(password: str, salt: bytes, options: Argon2Options) -> str

Security Contract:

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:

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:

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:

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:

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:

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

  1. Memory Protection: Clear all sensitive data after use
  2. Parameter Validation: Reject hashes with unsafe parameters
  3. Resource Limits: Set maximum memory to prevent DoS
  4. Rate Limiting: Implement per-user attempt limits
  5. 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:

  1. Start with recommended parameters
  2. Measure on target hardware
  3. Adjust to meet time budget (0.5-1s for interactive)
  4. Ensure memory doesn’t cause swapping
  5. Test under concurrent load

Platform-Specific Security Notes

Python

Rust

TypeScript/JavaScript

Swift

Kotlin

WASM

Compliance and Standards

Security Auditing

Verification Checklist

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

  1. Using Argon2 for Non-Password Cryptography
    • Argon2 is designed for passwords only
    • Use HKDF or similar for key derivation from high-entropy secrets
  2. Storing Parameters Separately from Hash
    • Always use encoded format that includes all parameters
    • Never assume parameters - always parse from hash
  3. Using in Timing-Sensitive Operations
    • Argon2 is deliberately slow and variable-time
    • Never use for MAC, signatures, or session tokens
  4. Insufficient Memory on Production Systems
    • Ensure system has 4x the memory parameter available
    • Account for concurrent operations
  5. 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:

For complete security analysis and risk assessment, see the dedicated threat model documentation.

References

  1. RFC 9106 - Argon2 Memory-Hard Function
  2. Password Hashing Competition
  3. OWASP Password Storage Cheat Sheet
  4. 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