Bcrypt vs SHA-256 for Passwords
SHA-256 is a checksum, not a password store. See unsalted hash failures, how bcrypt salt and cost work, and how to check hashes in the browser.
A signup form hashes password with SHA-256, stores the hex in Postgres, and the team calls it "encrypted." Months later a dump leaks. Every reused password and Password123! is already in a public rainbow table. The hash was one-way. It was still the wrong one-way function.
This guide is for the moment you need to pick a primitive: checksum a file, compare two strings, or store a user password. It walks through the failures that show up in code reviews and incident tickets, with disposable examples you can reproduce in Utilitoo's browser tools before you change production code.
Use throwaway strings only. Do not paste live user passwords, even into a local page.
What each hash is for
| Primitive | Job | Same input twice | Speed | Password storage |
|---|---|---|---|---|
| SHA-256 | Integrity fingerprint | Identical 64-char hex | Fast on purpose | No |
| MD5 | Legacy checksum | Identical 32-char hex | Very fast | Never |
| HMAC-SHA-256 | Prove a shared secret signed this message | Identical for same key+message | Fast | No (that is API auth) |
| bcrypt | Password KDF | Different each time (random salt) | Slow on purpose | Yes (or Argon2) |
SHA-256 answers "did this byte string change?" Bcrypt answers "can this guess be the password, without making offline guessing cheap?" Mixing those questions is the usual bug.
If the destination is a download page, a cache key, or a Git blob, use SHA-256. If a human will type the secret at login, use bcrypt (or Argon2id in new systems). Do not "upgrade" a password store by switching MD5 to SHA-256. Both are fast hashes.
Pitfall 1: The same password always hashes the same
SHA-256 is deterministic. That is a feature for checksums and a liability for passwords.
Example with the disposable string correct-horse-test-only. Paste it into SHA256 Generator and click Generate SHA-256. You get:
23a72ca2001d94f97282f8b3df686d130ffa4a18d550f544a6501d1fb0c665f9
Generate again. Same hex. Hash password and you get the well-known digest 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 - a value attackers already have in lookup tables.
Typical scene: Two users pick the same weak password. The password_hash column contains identical rows. A leak tells you exactly who reused the credential, and a GPU can test the rest of the dump at billions of SHA-256 guesses per second.
What to do: Do not store sha256(password) or md5(password). Confirm the failure mode yourself: generate the SHA-256 twice, then switch to Bcrypt Generator, set cost 10, and generate twice. You will get two different $2b$10$... strings for the same input. That difference is the salt working.
Pitfall 2: "I added a salt" but kept a fast hash
A common patch is sha256(salt + password) with a per-user salt in another column. That stops the simplest rainbow tables. It does not stop a dedicated cracker. SHA-256 still runs in nanoseconds. An attacker who has the salts hashes guesses just as fast, only without a precomputed table.
Bcrypt folds the salt into the output string and adds a cost factor (the 10 in $2b$10$...). Each increment roughly doubles the work. Cost 4 is for unit tests. Cost 10 is a common development default. Cost 12+ is closer to production policy, measured against login latency on your hardware.
Typical scene: A review comment says "we salt SHA-256 now." The threat model was GPU guessing, not rainbow tables. The patch did not change the threat.
HMAC is the same family of mistake when used as a password store. HMAC Generator is for webhook signatures and API request auth - a shared secret plus a message - not for hashing user passwords at registration.
Pitfall 3: Comparing the wrong strings
Bcrypt hashes are not 64-character hex. They look like:
$2b$10$tqTfSxuBd3PCh5rBJ8gmgeSU9HSmdrWD9XiFu3ehoY7J5ztAtd6FC
and
$2b$10$3pTBCDVMmH7adloz7elzje/Jv00OIu0yuGVXSqCVc37aPT1vETuAW
Those two strings are both valid hashes of correct-horse-test-only at cost 10. They are not equal to each other. Equality is not how you verify a password.
Typical scene: A developer pastes a $2b$10$... value into Hash Checker, selects SHA-256, and concludes "the password is wrong." Hash Checker compares SHA-256 or MD5 hex. It cannot verify bcrypt. Use Bcrypt Generator's Verify password mode: paste the plaintext and the $2... hash, then click Verify.
The other direction fails too. If you paste a SHA-256 hex into bcrypt verify, the tool should reject the format ($2a$ / $2b$ prefix). That is correct, not a bug.
Pitfall 4: MD5 "because the old system used it"
MD5 Generator still exists because download mirrors, etags, and ancient APIs ask for it. MD5 is collision-broken and even faster to brute-force than SHA-256. Using it for passwords is not a compatibility requirement; it is leftover copy-paste from a checksum tutorial.
If you must speak MD5, use it only as a non-security fingerprint, then confirm with Hash Checker against a published checksum. For anything a user types, migrate to bcrypt (or Argon2) with a proper rehash-on-login path - do not MD5-then-SHA-256 as a "layered" password scheme.
A password vs checksum checklist
- Name the job. File or payload integrity -> SHA-256. User password at rest -> bcrypt or Argon2. Webhook authenticity -> HMAC with a server-side secret.
- Look at the output. Hex of fixed length is a digest. A
$2a$/$2b$/$2y$string is bcrypt. Do not store one in a column named for the other. - Expect bcrypt hashes to differ. Generate the same test password twice. If the strings match, you are not using bcrypt (or you are hashing without a salt).
- Verify with the matching tool. SHA-256/MD5 -> Hash Checker. Bcrypt -> Verify password. Never
===two bcrypt hashes. - Pick cost with a stopwatch. Cost 10 for local fixtures; raise toward 12+ in production after you measure
comparelatency. Cost 15 is a noticeable wait in the browser - that is the point of a KDF, and why you do not use it for checksums. - Keep production hashing in your app. Browser tools are for fixtures, teaching, and comparing library output. The live login path should call a maintained library (
bcrypt,argon2) on the server.
How the Utilitoo tools fit
Reproduce the whole argument with disposable input:
- Invent a test secret in Password Generator (or type
correct-horse-test-only). Optionally score it in Password Strength Checker so you are not demonstrating withadmin. - Paste it into SHA256 Generator, generate twice, and copy both hex values. They match. Paste the text plus one hex into Hash Checker, choose SHA-256, click Verify - match.
- Paste the same text into Bcrypt Generator, cost 10, click Generate bcrypt hash. Copy the
$2b$10$...string. Generate again. The second string differs. Switch to Verify password, paste the original text and either hash, click Verify - both should match. - Deliberately paste a bcrypt string into Hash Checker as SHA-256. It will not match. That is the wrong comparator, which is the same class of bug as storing SHA-256 in a
bcrypt_hashcolumn.
Everything runs in the browser. Still treat whatever you type as visible on your screen. Prefer strings you would publish in a gist.
Common mistakes
| Mistake | What goes wrong | Better habit |
|---|---|---|
sha256(password) in the users table | Rainbow tables; GPU guessing | bcrypt or Argon2 via your app library |
| MD5 because a checksum tutorial used it | Broken and fast | MD5 only for legacy file labels |
Comparing two bcrypt hashes with === | Same password looks like a miss | bcrypt.compare / Verify password |
Pasting $2b$... into a SHA-256 checker | False mismatch | Hash Checker is for hex digests |
| Cost 4 in production | Cheap to brute-force | Cost 12+ after measuring latency |
| Hashing real user passwords in a demo | Credential exposure | Disposable test strings only |
Bottom line
SHA-256 and bcrypt are both one-way, and that is where the similarity ends. SHA-256 is a fast fingerprint. Bcrypt is a slow, salted password hash. If you can hash the same secret twice and get the same string, you are not storing passwords correctly.
Utilitoo's Bcrypt Generator, SHA256 Generator, and Hash Checker are for seeing that difference on a sample string before the review comment, the migration, or the leak.
Try these tools
- Bcrypt Generator - Generate bcrypt password hashes with adjustable cost.
- SHA256 Generator - Generate SHA-256 hashes from text.
- Hash Checker - Verify text against a known hash.
- MD5 Generator - Generate MD5 hashes from text.
- HMAC Generator - Generate HMAC signatures from text and a secret key.
