Encryption is two-way: with the key, you get the original data back. Hashing is one-way: it produces a fixed-size fingerprint, and there is no key and no way back. So encrypt data you need to read again, such as a backup or a credit card number. Hash data you only need to compare, such as a file’s checksum. Passwords need a third thing, a slow salted password hash, because ordinary fast hashes such as SHA-256 are the wrong tool there.
What a hash gives you
A cryptographic hash function takes any input and returns a fixed-length value. We ran these with Node’s crypto module:
MD5("hello") 5d41402abc4b2a76b9719d911017c592
MD5("Hello") 8b1a9953c4611296a827abf8c47804d7
SHA-256("hello") 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("Hello") 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
One changed letter gives an unrelated result. That behaviour is called the avalanche effect. A secure hash should provide three properties:
| Property | Meaning | What breaks if it fails |
|---|---|---|
| Preimage resistance | Given a hash, you can’t find an input for it | Attackers recover inputs |
| Second preimage resistance | Given a file, you can’t find another with the same hash | A file can be swapped for a fake |
| Collision resistance | You can’t find any two inputs with the same hash | Signatures on a document can be moved to another |
Collisions must exist, since infinite inputs map onto a finite number of outputs. “Secure” means nobody can find one in practice. SHA-256 has 2^256 possible outputs, so by the birthday bound a generic collision search takes around 2^128 tries, which is out of reach.
Why MD5 is broken
MD5’s collision resistance has failed completely, and you can check this yourself. These two 128-byte values, shown in hex, are different. They differ in six bytes:
d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f89
55ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5b
d8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70
d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f89
55ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5b
d8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70
Decoded to bytes and run through MD5, both gave 79054025255fb1a26e4bc422aef54eb4 in our test. SHA-256 tells them apart at once: one starts 8d12236e, the other b9fef2a8.
RFC 6151, from 2011, states that MD5 “is no longer acceptable where collision resistance is required such as digital signatures”, and cites attacks that find a collision in about a minute on a notebook PC.
What that does and does not mean. No practical attack is known that finds an input for a given MD5 hash. The break is in collisions, where the attacker controls both files. A signed innocent document can be swapped for its colliding malicious twin, and the signature stays valid. Using MD5 to spot accidental corruption or to deduplicate your own trusted files still works, but with SHA-256 widely available there is rarely a reason to start. And an MD5 of a short password can be cracked by brute force in moments, for the reasons below.
SHA-1 followed. In February 2017, researchers at CWI Amsterdam and Google published the first practical SHA-1 collision, two different PDF files with one hash. NIST has announced that SHA-1 should be phased out by December 31, 2030. Use SHA-256 or SHA-3 in new work.
Encryption, for comparison
Encryption uses a key to turn plaintext into ciphertext and back. AES-256-GCM is the common modern choice for symmetric encryption, and it also detects tampering. RSA and elliptic curve systems use a public and private key pair, mostly to exchange symmetric keys and to make signatures.
Three practical differences:
- Output size. A hash is a fixed size. Ciphertext grows with the input.
- Key management. Encryption means you have to protect, rotate and back up a key. Lose it and the data is gone. Leak it and the data is exposed.
- Purpose. Encryption protects confidentiality. A hash on its own protects nothing, because anyone can compute it. If an attacker can replace a file, they can replace the checksum beside it.
To prove a message came from someone with a key, use an HMAC, which mixes a secret into the hash. That is how HS256 JSON Web Tokens are signed. A plain hash can’t do this job.
Why passwords need something different
The obvious approach, storing SHA-256(password), is badly wrong. There are two reasons.
SHA-256 is designed to be fast. On a Mac running Node.js 22, one single JavaScript thread computed about 486,000 SHA-256 hashes per second in our test. Dedicated cracking hardware is many orders of magnitude faster. For most human passwords, trying candidates at that rate finishes quickly.
Identical passwords give identical hashes. SHA-256("password123") is always ef92b778bafe771e.... An attacker with a leaked table can look that up in a precomputed list, and can see at a glance which users share a password.
Salts fix the second problem
A salt is a random value unique to each user, stored in the clear beside the hash, and mixed into the input. Using two short example salts:
SHA-256("x7Kq2" + "password123") a4b695cb6fc743e7...
SHA-256("9fPzL" + "password123") 864bddcc839ddffd...
The same password now gives different hashes, so precomputed tables stop working and each account has to be attacked on its own. A salt isn’t a secret, and it doesn’t make guessing any slower for a single account.
Slow hashes fix the first
Password hashing functions make every guess expensive on purpose, and you can raise the cost as hardware improves. In the same test, a single scrypt derivation at N=2^17 took about 734 ms, and PBKDF2-HMAC-SHA256 at 600,000 iterations took about 534 ms. That is unnoticeable at login and ruinous for someone trying billions of guesses. Argon2 and scrypt go further and require large amounts of memory, which makes GPUs and custom hardware far less effective.
The OWASP Password Storage Cheat Sheet gives these minimums:
| Algorithm | Recommended setting | When |
|---|---|---|
| Argon2id | 19 MiB memory, 2 iterations, 1 degree of parallelism | First choice for new systems |
| scrypt | N=2^17 (128 MiB), r=8, p=1 | When Argon2id is unavailable |
| bcrypt | Work factor 10 or more | Legacy systems |
| PBKDF2-HMAC-SHA256 | 600,000 iterations | When FIPS-140 compliance is required |
bcrypt has one sharp edge: most implementations accept only 72 bytes of input, so a longer passphrase is either cut off or rejected. Plan for this when setting password rules.
Use a library for any of these. They generate the salt and store the parameters inside the output string, so you can raise the cost later and rehash each user when they next log in. Don’t write the salting yourself.
from argon2 import PasswordHasher
ph = PasswordHasher()
stored = ph.hash("correct horse battery staple")
ph.verify(stored, "correct horse battery staple") # raises if wrong
That uses the argon2-cffi package, whose defaults exceed the minimums above. Check them against your server’s memory budget.
Using our hash generator
Our Hash Generator computes MD5, SHA-1, SHA-256, SHA-384 and SHA-512 in your browser, from text you type or from a file of up to 512 MB, and it can compare the result against a checksum you were given. That suits checking a download, seeing how hashes behave, and debugging an API signature. MD5 is included so you can match old checksums, and the tool labels it as broken.
Text is hashed as UTF-8, and every byte counts. A trailing newline changes everything, and it’s the usual reason a result differs from echo "hello" | sha256sum. (echo adds a newline, so use printf.)
It is not for storing passwords. None of those algorithms is suitable, and it has no salt or cost setting. If you need a strong password to begin with, the Password Generator makes one.
Which to use
| Goal | Use |
|---|---|
| Read the data back later | Encryption, AES-256-GCM |
| Store user passwords | Argon2id |
| Verify a file downloaded correctly | SHA-256 checksum |
| Detect tampering with a shared secret | HMAC-SHA-256 |
| Prove authorship to anyone | A digital signature, Ed25519 or ECDSA |
| Move binary through text | Base64, which is neither hash nor encryption |