A complete, RFC 9106 compliant implementation of the Argon2 password hashing algorithm in V.
- ✅ Complete Implementation: All three Argon2 variants (Argon2i, Argon2d, Argon2id)
- ✅ RFC 9106 Compliant: Matches official test vectors exactly
- ✅ PHC String Format: Full support for Password Hashing Competition string format
- ✅ Security: Constant-time operations and proper memory management
- ✅ Performance: Optimized compression function based on Blake2b
- ✅ Test-Driven: Comprehensive test suite with official vectors
import fleximus.argon2
import crypto.rand
// Generate a secure random salt
salt := rand.bytes(16) or { panic(err) } // 128-bit salt
// Hash with secure defaults (Argon2id, t=3, m=64MB, p=4)
hash := argon2.hash('mypassword'.bytes(), salt) or { panic(err) }
println(hash) // $argon2id$v=19$m=65536,t=3,p=4$...
// Verify password (auto-detects variant)
is_valid := argon2.verify(hash, 'mypassword'.bytes()) or { panic(err) }
if is_valid {
println('Password verified!')
}import fleximus.argon2
// Hash with custom parameters
hash := argon2.hash_with_params(2, // t_cost: 2 iterations
32768, // m_cost: 32 MB memory
2, // parallelism: 2 threads
'mypassword'.bytes(), // password
'somesalt'.bytes(), // salt
32 // hashlen: 32-byte output
) or { panic(err) }
// Verify (still auto-detects variant)
is_valid := argon2.verify(hash, 'mypassword'.bytes()) or { panic(err) }import fleximus.argon2
// Hash with secure defaults: Argon2id, t=3, m=64MB, p=4, 128-bit salt, 256-bit output
// The default hash function uses Argon2id for maximum security
password := 'mypassword'.bytes()
salt := 'mysalt1234567890'.bytes() // 16 bytes for default security
hash := argon2.hash(password, salt) or { panic(err) }import fleximus.argon2
// Hash with custom parameters using Argon2id
password := 'mypassword'.bytes()
salt := 'mysalt123456789'.bytes()
hash := argon2.hash_with_params(3, // t_cost: Time cost (iterations)
65536, // m_cost: Memory cost (KB)
1, // parallelism: Parallelism degree
password, // Password bytes
salt, // Salt bytes (min 8 bytes)
32 // hashlen: Output hash length
) or { panic(err) }import fleximus.argon2
// Verify password against any Argon2 PHC string (auto-detects variant)
encoded := r'$argon2id$v=19$m=65536,t=3,p=1$...'
password := 'mypassword'.bytes()
is_valid := argon2.verify(encoded, password) or { panic(err) }import fleximus.argon2
// Generate encoded hash
password := 'mypassword'.bytes()
salt := 'mysalt123456789'.bytes()
hash := argon2.hash_id(3, // t_cost: Time cost (iterations)
65536, // m_cost: Memory cost (KB)
1, // parallelism: Parallelism degree
password, // Password bytes
salt, // Salt bytes (min 8 bytes)
32 // hashlen: Output hash length
) or { panic(err) }
// Generate raw hash
hash_raw := argon2.hash_id_raw(3, // t_cost: Time cost (iterations)
65536, // m_cost: Memory cost (KB)
1, // parallelism: Parallelism degree
password, // Password bytes
salt, // Salt bytes (min 8 bytes)
32 // hash_len: Output hash length
) or { panic(err) }
// Verify password against the generated hash
is_valid := argon2.verify_id(hash, password) or { panic(err) }import fleximus.argon2
// For side-channel resistant environments
password := 'mypassword'.bytes()
salt := 'mysalt123456789'.bytes()
// Generate hash first
hash := argon2.hash_i(3, 65536, 1, password, salt, 32) or { panic(err) }
hash_raw := argon2.hash_i_raw(3, 65536, 1, password, salt, 32) or { panic(err) }
// Verify password against the generated hash
is_valid := argon2.verify_i(hash, password) or { panic(err) }import fleximus.argon2
// For maximum resistance against GPU attacks
password := 'mypassword'.bytes()
salt := 'mysalt123456789'.bytes()
// Generate hash first
hash := argon2.hash_d(3, 65536, 1, password, salt, 32) or { panic(err) }
hash_raw := argon2.hash_d_raw(3, 65536, 1, password, salt, 32) or { panic(err) }
// Verify password against the generated hash
is_valid := argon2.verify_d(hash, password) or { panic(err) }| Use Case | t_cost | m_cost | parallelism |
|---|---|---|---|
| High Security | 3-4 | 65536-131072 | 1-4 |
| Interactive | 2-3 | 32768-65536 | 1-2 |
| Low Latency | 1-2 | 16384-32768 | 1 |
import fleximus.argon2
// Parameter limits
println('Min output: ${argon2.min_outlen}') // 4 bytes
println('Max output: ${argon2.max_outlen}') // 2^32-1 bytes
println('Min salt: ${argon2.min_salt_length}') // 8 bytes
println('Min memory: ${argon2.min_memory}') // 8 blocks (32 KB)
println('Sync points: ${argon2.sync_points}') // 4 synchronization points
// Recommended defaults
println('Default t_cost: ${argon2.default_t_cost}') // 3 iterations
println('Default m_cost: ${argon2.default_m_cost}') // 65536 KB (64 MB)
println('Default parallelism: ${argon2.default_parallelism}') // 4 threads
println('Default hash len: ${argon2.default_hash_len}') // 32 bytesimport fleximus.argon2
import crypto.rand
fn register_user(username string, password string) !string {
// Generate random salt (128 bits)
salt := rand.bytes(16) or { return err }
// Hash password with secure defaults
hash := argon2.hash(password.bytes(), salt) or { return err }
// Store username and hash in database
println('User: ${username}')
println('Hash: ${hash}')
return hash
}import fleximus.argon2
fn authenticate_user(stored_hash string, password string) !bool {
// Auto-detects Argon2 variant and verifies
return argon2.verify(stored_hash, password.bytes())
}
// Usage
hash := r'$argon2id$v=19$m=65536,t=3,p=4$...$...'
is_valid := authenticate_user(hash, 'user_password') or { false }
if is_valid {
println('Login successful!')
} else {
println('Invalid password!')
}import fleximus.argon2
fn hash_with_custom_params() !string {
return argon2.hash_with_params(4, // t_cost: 4 iterations for extra security
131072, // m_cost: 128 MB memory usage
2, // parallelism: Use 2 threads
'secret'.bytes(), // password
'unique_salt_16_bytes!'.bytes(), // salt
64 // hashlen: 64-byte output
)
}import fleximus.argon2
fn compare_variants() {
password := 'test_password'.bytes()
salt := 'random_salt_16_b'.bytes() // Exactly 16 bytes
println('Comparing Argon2 variants...')
// Test each variant individually with error handling
println('Testing Argon2i...')
hash_i := argon2.hash_i_raw(2, 65536, 1, password, salt, 32) or {
eprintln('Argon2i failed: ${err}')
return
}
println('Testing Argon2d...')
hash_d := argon2.hash_d_raw(2, 65536, 1, password, salt, 32) or {
eprintln('Argon2d failed: ${err}')
return
}
println('Testing Argon2id...')
hash_id := argon2.hash_id_raw(2, 65536, 1, password, salt, 32) or {
eprintln('Argon2id failed: ${err}')
return
}
// Display results
println('Results:')
println('Argon2i: ${hash_i.hex()}')
println('Argon2d: ${hash_d.hex()}')
println('Argon2id: ${hash_id.hex()}')
}- Use Argon2id for most applications (hybrid security)
- Use Argon2i in side-channel sensitive environments
- Use Argon2d for maximum GPU resistance (not recommended for most uses)
- Generate random salts (minimum 8 bytes, recommended 16+ bytes)
- Adjust parameters based on your security vs. performance requirements
- Use constant-time comparison when verifying hashes
The implementation is optimized for correctness and security. Performance characteristics:
- Memory: Configurable from 32 KB to 4 GB
- CPU: Scales with t_cost parameter
- Parallelism: Supports multi-threading (p > 1)
Benchmark on typical hardware (adjust parameters based on your requirements):
- t=2, m=65536, p=1: ~100ms
- t=3, m=65536, p=1: ~150ms
- t=2, m=131072, p=1: ~200ms
Run the comprehensive test suite:
v test .The library includes:
- Official RFC 9106 test vectors
- Parameter variation tests
- Input variation tests
- Error condition tests
- PHC string format tests
This implementation follows RFC 9106 exactly:
- Initial Hash (H₀): Blake2b of parameters and inputs
- Memory Initialization: Generate first blocks using Blake2b-long
- Memory Filling:
- Argon2i: Data-independent addressing with precomputed indices
- Argon2d: Data-dependent addressing using previous block
- Argon2id: Hybrid approach combining both methods
- Compression: fBlaMka function with Blake2 rounds
- Finalization: XOR last blocks and Blake2b-long for output
MIT License - see LICENSE file for details.
Contributions welcome! Please ensure:
- All tests pass
- New features include tests
- Code follows V style guidelines
- Security considerations are documented