Skip to content
Free Tool Arena

Coding & Tech · Guide

How to hash passwords

Why you never store plain passwords, why MD5/SHA-1 aren't enough for auth, and how bcrypt/Argon2 actually protect users.

Updated April 2026 · 6 min read

Password storage is one of the easiest things to get wrong and one of the most catastrophic when you do. Every year a major company leaks a database full of either plaintext or badly-hashed passwords, and every year developers keep making the same mistakes: MD5, SHA-256 without a salt, homemade obfuscation. This guide covers what a hash actually is, why the fast ones are wrong for passwords, and what to use instead.

Hash vs encryption

A hash is one-way. You can compute it from the input but you cannot reverse it. Given the hash, the only way to find the input is to guess inputs and check whether their hashes match.

Encryption is reversible — there’s a key, and with the key you can get the original back. Encryption is the wrong tool for password storage, because if an attacker gets the database they’ll probably get the key too, and then all the passwords are plaintext. Hashing removes the key from the equation entirely.

Why plaintext is the worst mistake

Storing passwords as-is means any database leak is game over. Not just for your site — password reuse is universal, so your leaked plaintext hands attackers the credentials for your users’ email, bank, and everything else. Users can run their email through a breach checkerand see what spilled, but by then the damage is done. Never, ever store plaintext.

Why plain SHA-256 is also wrong

SHA-256 is a fast, secure cryptographic hash. That’s great for file integrity checks and bad for passwords. A modern GPU can compute roughly ten billion SHA-256 hashes per second. An attacker with a leaked database and a $2,000 GPU can try every 8-character password in a weekend.

The speed that makes SHA-256 great for checksums makes it terrible for password storage. You want the opposite: a hash that’s deliberately slow so that even with a stolen database, brute-forcing it is economically unviable.

What bcrypt, Argon2, and scrypt add

These are password-specific hashing functions designed to be slow and memory-hungry on purpose. bcrypt has been the default for 20 years and is still fine. Argon2 won the Password Hashing Competition in 2015 and is the current recommendation for new projects. scrypt sits between them.

All three let you tune a work factor — how much CPU and memory a single hash costs. The number is set so that your login endpoint takes ~100-250ms (acceptable for users) but an attacker trying to brute-force a stolen database is doing ~5 hashes per second per core instead of billions. That gap is the whole point.

Salt: unique per user

A salt is a random value, unique per user, stored next to the hash. When you hash the password you hash password + salt. The purpose: without a salt, two users with the same password produce the same hash, and an attacker can pre-compute hashes for common passwords (rainbow tables) and match millions at once. With a unique salt per user, the attacker has to attack each user individually — a massive slowdown.

Modern libraries generate and store the salt for you — you don’t pick it, you don’t manage it. bcrypt embeds the salt inside the stored hash string. If you’re manually managing salts, you’re using the wrong library.

Pepper: one global secret

A pepper is an application-wide secret added to the hash input, stored outside the database (in an env var, secret manager, or HSM). The idea: if an attacker steals the database but not the secret store, they can’t brute-force even weak passwords because they’re missing the pepper. Optional but cheap defense-in-depth.

The rule: don’t roll your own

For authentication, use the well-audited library your platform ships. Node:bcrypt or argon2. Python: passlib orargon2-cffi. Go: golang.org/x/crypto/bcrypt. Ruby/Rails:bcrypt via has_secure_password. They handle salt generation, work factor tuning, and timing-safe comparison. If you’re writing the hash code yourself, you’re almost certainly introducing a bug.

General-purpose hashing is different

For file integrity, checksums, content-addressed storage, or deduplication, SHA-256 is exactly right. It’s fast, collision-resistant, and standardized. Generate one with our hash generator. The “deliberately slow” rule is password-specific — you don’t want your git commit hashes to take 200ms each. Use the right hash for the job: Argon2 or bcrypt for passwords, SHA-256 for everything else.