Weak Cryptography
debt(d5/e5/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep, psalm, and phpstan with automated:yes and specific code_patterns (md5, sha1, ECB mode, etc.). These are not default linters but specialist SAST/static-analysis tools that need configuration to flag weak crypto patterns. Not caught by the compiler or a default linter pass.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to replace MD5/SHA1/DES/RC4 with AES-256-GCM or Argon2id, but common_mistakes reveal this spans passwords, tokens, encryption modes, and IV handling. Each call site must be updated and data re-hashed or re-encrypted, often requiring a migration plan for existing stored hashes — more than a one-liner swap but not necessarily a cross-cutting architectural rewrite.
Closest to 'persistent productivity tax' (b5). applies_to covers web, cli, and queue-worker contexts, meaning weak crypto choices can appear across multiple parts of a PHP codebase. Fixing it isn't trivial — stored password hashes or encrypted data may need migration — but the burden doesn't reshape the entire system architecture the way a fundamental ORM or microservices choice would.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception explicitly states 'MD5 and SHA-1 are fine for non-password hashing,' which is a widely-held but dangerous belief. Developers who correctly understand not to use MD5 for passwords still commonly assume it's safe for file integrity or digital signatures, not realising collision attacks break those use cases too. This contradicts reasonable analogical reasoning from the 'MD5 is fast and unique enough for checksums' mental model.
Also Known As
TL;DR
Explanation
MD5 and SHA1 were designed for speed and data integrity, not security. They are not suitable for password hashing because they are so fast that an attacker with the hash can test billions of guesses per second on commodity hardware. Rainbow tables and precomputed collision databases exist for common MD5/SHA1 hashes. For passwords, use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID. For tokens, use bin2hex(random_bytes(32)). For general integrity, use hash('sha256', ...).
Common Misconception
Why It Matters
Common Mistakes
- Using MD5 or SHA1 for password storage — both are fast-hash functions not designed for passwords.
- Using PHP's rand() or mt_rand() for security tokens — they are not cryptographically secure PRNGs.
- Using mcrypt (removed in PHP 7.2) or ECB mode, which encrypts identical blocks identically revealing patterns.
- Reusing the same IV across multiple encryptions with the same key.
Avoid When
- Never use MD5 or SHA-1 for passwords, security tokens, or digital signatures — they are broken.
- Never use ECB mode for block cipher encryption — it leaks patterns in the plaintext.
When To Use
- Use AES-256-GCM (via sodium or openssl) for symmetric encryption of sensitive data at rest.
- Use SHA-256 or SHA-3 for data integrity checksums where collision resistance matters but password hashing is not needed.
Code Examples
// MD5/SHA1 are fast hash functions — not designed for passwords
\$hash = md5(\$password);
// ECB mode leaks patterns — same plaintext → same ciphertext block
\$encrypted = openssl_encrypt(\$data, 'AES-128-ECB', \$key);
// Passwords: always use password_hash() (bcrypt/Argon2id)
\$hash = password_hash(\$password, PASSWORD_ARGON2ID);
// Symmetric encryption: AES-256-GCM (authenticated encryption)
\$iv = random_bytes(12); // 96-bit IV for GCM
\$tag = '';
\$encrypted = openssl_encrypt(\$plaintext, 'AES-256-GCM', \$key, 0, \$iv, \$tag);
\$stored = base64_encode(\$iv . \$tag . \$encrypted);
// Decrypt + verify tag
\$raw = base64_decode(\$stored);
\$iv = substr(\$raw, 0, 12);
\$tag = substr(\$raw, 12, 16);
\$ciphertext = substr(\$raw, 28);
\$plaintext = openssl_decrypt(\$ciphertext, 'AES-256-GCM', \$key, 0, \$iv, \$tag);