glunty

Blog   /   guides   /  

How to hash passwords (bcrypt)

Learn how to hash passwords safely with bcrypt: why fast hashes like MD5 fail, what salt and cost factor do, and why hashing runs server-side.

If your application stores passwords, the most important rule is that it must never store the password itself. It stores a hash: a one-way transformation you can verify a login against but cannot reverse into the original text. Get this wrong and a single database leak hands an attacker every account. This guide explains how to hash passwords correctly, why bcrypt is a standard answer, and where the work must happen.

Why you never store plaintext passwords

Databases leak: backups get copied to laptops, logs capture request bodies, servers get misconfigured. When that happens, plaintext passwords are catastrophic because people reuse them. The email and password from your leaked table probably unlocks the same person’s bank, inbox, and employer. You are not just protecting your own service but every account that shares the credential.

A hash means the leaked table contains no usable secrets. To check a login, you hash the submitted password and compare it to the stored hash. The original text never needs to exist at rest.

Why fast hashes (MD5, SHA-256) are wrong for passwords

Here is the trap that catches well-meaning developers. MD5 and SHA-256 are cryptographic hash functions, and they are one-way, so surely they are fine? They are not, and the reason is speed. These functions were designed to be fast, because their real jobs (file checksums, digital signatures, integrity checks) need to process large amounts of data quickly. Speed is exactly the wrong property for a password hash.

A modern GPU can compute billions of SHA-256 hashes per second. An attacker with your leaked table does not try to reverse the hash; they guess. They run every common password and leaked-password list through the same fast hash and compare results. At billions of guesses per second, most human-chosen passwords fall in minutes. You can see how a raw SHA-256 or MD5 digest looks using the hash generator, but seeing it should reinforce the point: it computes instantly, which is precisely why it fails here.

What makes a password hash right

A good password hashing algorithm is deliberately slow, salted, and adaptive.

Slow means each hash takes a meaningful fraction of a second. That is invisible to a user logging in once, but it turns an attacker’s billions-per-second into thousands-per-second, turning a five-minute crack into years.

Salted means a unique random value is mixed into each hash. Without a salt, two users with the same password get identical hashes, and one cracked hash breaks every matching account at once. A per-user salt means every hash is unique, and precomputed rainbow tables become useless because the attacker would need a fresh table for every salt.

Adaptive means the algorithm has a tunable cost you can raise over time. Hardware keeps getting faster, so a fixed difficulty erodes; an adaptive function lets you increase the work to stay ahead. The three widely trusted choices that do all of this are bcrypt, scrypt, and argon2. Argon2 is the newest and is often recommended for new projects, but bcrypt has decades of scrutiny and remains a completely reasonable, well-supported default.

What the bcrypt cost factor does

Bcrypt has a parameter usually called the cost factor or work factor, written as a number like 10, 12, or 14. It is a base-2 exponent: the number of internal iterations is 2 raised to that cost. So a cost of 12 does twice the work of 11.

This is the adaptive dial. You pick a cost that keeps a single hash in a comfortable range for your server (often somewhere around a tenth of a second), and you raise it every few years as hardware improves. Experiment with the bcrypt tool to feel this directly: bump the cost up by one and the hash visibly takes longer to compute. That extra time is the whole point: it multiplies across every guess an attacker makes.

The salt is embedded in the bcrypt output

A common question is where to store the salt. With bcrypt you do not manage it separately: bcrypt embeds the salt inside the hash string it produces. A bcrypt output like $2b$12$... encodes the version, the cost factor, the 22-character salt, and the hash, all in one field. When a user logs in, your library reads the cost and salt from the stored string, rehashes the submitted password, and compares. You store one column, and everything needed to verify is inside it.

Hashing must happen on the server

One critical boundary: password hashing belongs on the server, not the browser. If you hash on the client and send the result, that hash effectively becomes the password, and anyone who steals it can replay it. The raw password should travel over HTTPS to your backend, where the server hashes it and stores only the result. A browser-based bcrypt tool is great for learning and testing the output format, but it is not where real authentication should run.

Putting it together

Choose bcrypt, scrypt, or argon2. Let the library generate a per-user salt. Set a cost factor that is comfortably slow, and revisit it over time. Do the hashing server-side, store the single hash string, and verify by rehashing. Finally, encourage strong passwords, since a slow hash buys time but cannot rescue a password on every leaked list; the password strength checker shows why length matters more than swapping letters for symbols.

Embedded tool from glunty.com