“MD5 is broken” is true but incomplete. What broke is collision resistance. Researchers showed in 2004 that you can construct two different inputs with the same MD5 hash, and doing so is now fast and well documented. That matters a lot if the hash is supposed to prove something wasn't tampered with. It matters much less if you only use the hash to catch accidental corruption, like checking that a file downloaded correctly.
Where each one is still fine
- MD5: checking a downloaded file against a published checksum, generating a cache key from non-sensitive input, deduplicating identical blobs. Nobody is trying to forge a collision against your CDN cache key.
- SHA-256: anything where an adversary could benefit from forging a match. Digital signatures, blockchain, verifying software hasn't been tampered with, Git commit hashes (historically SHA-1, now migrating to SHA-256).
Neither one is for passwords
This is the mistake that does the damage. MD5 and SHA-256 are both fast by design. That's good for checksums and bad for password storage, because an attacker with a leaked hash database can try billions of guesses per second on cheap hardware. Passwords need a deliberately slow password-hashing function: bcrypt, or better, a memory-hard one like scrypt or argon2, which also makes GPU cracking expensive. If a codebase stores passwords with raw SHA-256 (or worse, MD5), flag it, however good the rest of the code looks.
SHA-1 and SHA-512, briefly
SHA-1 sits between the two. Google demonstrated a practical collision in 2017 (the “SHAttered” attack), but SHA-1 still shows up in legacy systems and in Git's object model. Treat it like MD5: fine for non-adversarial integrity checks, not for anything security-sensitive. SHA-512 is SHA-256's bigger sibling, with the same security properties and a larger output. It's slightly slower on 32-bit systems and slightly faster on 64-bit ones. Pick SHA-256 unless you have a specific reason to want the larger digest.