A well-built site never has your actual password after you set it — only a hash of it. That much is common knowledge. What's less obvious is that "hash it" is nowhere near enough on its own: the same fast, general-purpose hash functions that are perfect for checksums and fingerprints are actively dangerous for passwords, and using one is one of the most common real-world security mistakes.
Why SHA-256 (or MD5, or SHA-512) is the wrong tool
General-purpose hash functions are built to be fast — hashing a large file in milliseconds is a feature. That's exactly backwards for a password: speed is what lets an attacker who steals your password database try billions of guesses per second on commodity GPU hardware. Combine that with how predictable real human passwords are — dictionary words, names, keyboard patterns, minor variations of "password123" — and a fast hash function turns a stolen database into a solved one within hours, not because the hash function is broken, but because it was never designed to resist this kind of attack in the first place. See MD5 vs SHA-256 vs SHA-512 for how fast these algorithms actually are — the same speed that makes them great for checksums makes them a liability here. SHA-256 gets singled out often enough, precisely because it isn't broken like MD5, that it has its own dedicated writeup: Why SHA-256 Should Not Be Used for Passwords, with real GPU and Bitcoin-ASIC throughput numbers attached.
Salting: necessary, but not sufficient
A salt is a random value generated per password and stored alongside its hash. Without one, two users with the same password get the same hash, and an attacker can precompute a rainbow table — a lookup table mapping common passwords to their hashes — once, and reuse it against every stolen database that used the same hash function. A salt defeats precomputation: the attacker now has to attack each hash individually, since the same password produces a different hash for every user.
What salting doesn't do is slow anything down. A salted SHA-256 hash is still exactly as fast to compute as an unsalted one, which means brute force and dictionary attacks against any individual account are just as cheap as before. Salting solves the "attack everyone at once" problem, not the "attack one account fast" problem — that second problem needs a fundamentally different kind of algorithm. For the mechanics of how salting actually defeats rainbow tables, exactly where a salt lives inside a real bcrypt or Argon2 hash, and what a "pepper" adds on top, see Understanding Salt and Pepper.
The fix: deliberately slow hashing
Password hashing algorithms are built around the opposite goal of a checksum: make each individual guess expensive, on purpose, in a way that's tunable as hardware gets faster over time. A few thousand hashes per second is irrelevant friction for a real login (one hash, once, per attempt) but devastating for an attacker trying billions of guesses against a stolen database.
- bcrypt (1999) — built on the Blowfish cipher, with a tunable cost factor that exponentially increases the number of internal rounds. Widely supported and battle-tested, though purely CPU-bound, which makes it somewhat more parallelizable on GPUs than memory-hard alternatives. One real gotcha: bcrypt silently ignores any password bytes past 72 — a rarely-hit but genuine limitation worth knowing about.
- scrypt (2009) — adds memory-hardness on top of CPU cost: computing it requires a large, tunable amount of RAM per guess. GPUs have thousands of compute cores but comparatively little memory per core, so a memory-hard function narrows the parallelization advantage that makes GPU cracking so effective against CPU-only algorithms. See scrypt Explained for exactly how its ROMix core creates that memory-hardness.
- Argon2 (2015) — winner of the Password Hashing Competition and the current default recommendation. It exposes three independent cost parameters — memory, time, and parallelism — tuned separately. The Argon2id variant is the generally recommended mode, balancing resistance to GPU/ASIC cracking with resistance to side-channel attacks.
- PBKDF2 (2000, NIST-approved) — repeatedly applies a standard hash function (commonly SHA-256) tens or hundreds of thousands of times. Legitimate and still common (WPA2, iOS/Android keychains), but purely iteration-based with no memory-hardness, which makes it the weakest of the four against GPU and ASIC attacks. A reasonable choice when a platform mandates NIST-approved primitives; not the first choice otherwise. See PBKDF2 Explained for exactly how its chained, XOR'd construction works and why it lacks memory-hardness.
For a deeper head-to-head between the two most commonly debated options, see Bcrypt vs Argon2 — why Argon2's configurable memory-hardness actually matters against GPU/ASIC cracking, and honest guidance on when sticking with an existing bcrypt deployment is fine. Once you've picked one, Password Hashing Best Practices covers what comes next — current recommended parameters, upgrading cost factors without a migration, and a couple of failure modes worth knowing about.
What this looks like in practice
You never call a raw hash function directly for this. A library implementing one of the algorithms above handles salt generation, encodes the cost parameters into the stored value itself (so they can be tuned upward later without breaking old hashes), and provides a verify(password, storedHash) function that recomputes the hash with the stored salt and parameters, then compares the result using a constant-time comparison — a plain === string comparison can leak timing information about how many characters matched, which is exactly the kind of detail a well-designed library handles for you so you don't have to think about it. For exactly what that looks like in real code, see How to Hash Passwords in Node.js, Python, or Java.
Common mistakes worth avoiding
- Using a raw hash function, salted or not. MD5, SHA-1, SHA-256, and SHA-512 are all wrong here, regardless of how many times you loop-and-rehash them by hand — that's reinventing a weaker, unaudited version of PBKDF2.
- Reversible encryption instead of hashing. Encrypting passwords with a symmetric key means anyone who obtains that key — from a config leak, a compromised server, an insider — recovers every password at once. A one-way hash has no equivalent single point of failure.
- Hand-rolling the comparison. Always use the algorithm library's own verify function rather than comparing hash strings yourself.
- Picking cost parameters once and never revisiting them. Hardware gets faster; a cost factor that was solid five years ago may not be today. Recommended parameters shift over time — checking current guidance (OWASP's cheat sheet series is a reliable source) beats hardcoding a number you read once.
Where this site's hashing tools fit in
Most hashing tools on this site — Hash Generator, Hash Verifier, Checksum Generator, and File Hash Calculator — compute general-purpose, fast hashes: exactly right for verifying file integrity or fingerprinting text, exactly wrong for password storage, for all the reasons above. What you can do client-side is generate strong source material for a password in the first place — see Password Generator, which runs entirely in your browser using the Web Crypto API's cryptographically secure random number generator.
The Password Hash Generator is the exception — it does compute real bcrypt, scrypt, Argon2, and PBKDF2 hashes, using the actual algorithms this post describes. It's built for seeding a database with test users, generating fixtures for a test suite, or checking what a given cost factor or memory setting actually produces — not for a real user's live password in an actual signup or login flow. That still has to happen server-side with a maintained library, never on a general-purpose web page, hashed client-side or not.
Going the other direction — you already have a hash and want to know what it is — Password Hash Inspector identifies whether a pasted hash is bcrypt, Argon2, SHA-256, or MD5 and decodes whatever cost factor, salt, or memory/iteration settings it has embedded, which is a handy way to see the difference this post describes made concrete: a bcrypt or Argon2 hash decodes into real parameters, while a bare SHA-256 or MD5 hash is just a fixed-length digest with nothing else in it.