Argon2 Password Hashing
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The term's detection_hints list phpstan and semgrep as tools that can detect usage of PASSWORD_BCRYPT when PASSWORD_ARGON2ID is available, or low bcrypt cost factors. These are specialist SAST/static-analysis tools rather than default linters, so d5 fits well. A default PHP linter won't flag this; you need configured semgrep rules or phpstan extensions.
Closest to 'simple parameterised fix' (e3). The quick_fix is essentially swapping PASSWORD_BCRYPT or PASSWORD_DEFAULT to PASSWORD_ARGON2ID in password_hash() calls — a one-line change per call site. However, common_mistakes include setting memory cost too low and using === instead of password_verify(), which may require touching multiple call sites across the codebase. The core fix is near e1 but addressing all related mistakes (memory cost tuning, ensuring password_verify usage, verifying hash storage format) pushes it to e3.
Closest to 'localised tax' (b3). The choice of password hashing algorithm is typically confined to the authentication layer — one component (user registration/login) pays the cost, and the rest of the codebase is unaffected. It applies to web and cli contexts but the actual code surface is narrow. It doesn't define the system's shape or impose a persistent productivity tax beyond the auth module.
Closest to 'notable trap' (t5). The misconception field states that developers believe 'any Argon2 variant is equally secure,' when in fact Argon2i is vulnerable to GPU cracking and Argon2d to side-channel attacks — only Argon2id combines both protections. Additionally, common mistakes like assuming PASSWORD_DEFAULT selects Argon2 on older PHP, setting memory cost too low (negating the entire benefit), and using === instead of password_verify() represent documented gotchas that most devs eventually learn but are genuinely surprising at first encounter.
Also Known As
TL;DR
Explanation
Argon2 comes in three variants: Argon2d (GPU resistance), Argon2i (side-channel resistance), and Argon2id (recommended — hybrid of both). It has three tunable cost parameters: memory (in KiB), time (iterations), and parallelism (threads), making it far more flexible than bcrypt's single cost factor. In PHP 7.2+, use PASSWORD_ARGON2ID with password_hash(). The OWASP recommendation is Argon2id with at least 19 MiB memory, 2 iterations, and 1 parallelism thread as a minimum configuration.
Diagram
flowchart TD
PASSWORD2[Password] --> PARAMS[Configure params<br/>memory_cost time_cost threads]
PARAMS --> FILL[Fill memory with pseudo-random data<br/>memory-hard - GPU resistant]
FILL --> DERIVE[Derive hash output]
DERIVE --> ENCODED[Encoded string<br/>algo + params + salt + hash]
subgraph vs_bcrypt
ARG_WIN[Argon2id wins<br/>memory-hard - defeats GPU farms<br/>configurable all dimensions<br/>Winner of PHC 2015]
BC_WEAK[bcrypt weakness<br/>max 72 chars truncated<br/>not memory-hard]
end
subgraph PHP
PHP_HASH[PASSWORD_ARGON2ID<br/>password_hash built-in]
end
style FILL fill:#6e40c9,color:#fff
style ARG_WIN fill:#238636,color:#fff
style BC_WEAK fill:#f85149,color:#fff
style PHP_HASH fill:#1f6feb,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using PASSWORD_DEFAULT in password_hash() and not knowing it may not select Argon2 on older PHP.
- Setting memory cost too low (less than 65536 KB) and negating the memory-hardness benefit.
- Comparing hashes with === instead of password_verify(), which breaks timing-safe comparison.
- Storing the raw hash output without the algorithm prefix, losing upgrade path information.
Code Examples
password_hash($pwd, PASSWORD_BCRYPT); // bcrypt fine but Argon2id preferred
password_hash($pwd, PASSWORD_ARGON2ID, ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 1]);