Password hashing: salts, bcrypt and Argon2 explained
Why fast hashes fail for passwords, how salts and work factors stop cracking, current bcrypt, scrypt and Argon2id settings, peppers, and upgrading legacy MD5 hashes.
On this page 10 sections
Password hashing means storing a one-way, deliberately slow fingerprint of each password instead of the password itself, so a stolen database doesn't hand attackers everyone's logins. Done properly it combines a unique random salt for every password, a deliberately slow algorithm such as Argon2id, scrypt or bcrypt, and a cost setting you raise over time. Plain SHA-256, MD5 and reversible encryption are the wrong tools for this job.
Why passwords need special hashing
General-purpose hashes such as SHA-256 are designed to be fast, which is exactly what you want for checking a 2 GB file and exactly what you don't want for passwords. Once a database leaks, attackers guess offline, at the full speed of their hardware, with no login screen or rate limit in the way. And human passwords are predictable: names, dates, cricket teams and keyboard patterns come early in any guessing list.
A worked example shows how much speed matters. The rates below are illustrative, not measurements, because real figures depend on hardware and settings.
- An 8-character password of lower-case letters and digits has 36 to the power 8 possibilities: about 2.8 trillion, or 2.8 lakh crore.
- If the stored hash lets an attacker try 10 billion guesses a second, checking every possibility takes under five minutes.
- If a slow password hash cuts that to 10,000 guesses a second, the same search takes almost nine years.
The password didn't change; only the cost of each guess did. That is the whole idea behind password hashing, and it is why passwords must never be stored encrypted either: anything that can be decrypted can be decrypted by whoever steals the key. For the difference between these operations, see encoding vs encryption vs hashing.
Salts
A salt is a random value generated separately for each password, combined with it before hashing and stored next to the hash. It isn't secret. It does two jobs:
- Identical passwords get different hashes, so an attacker can't see that 300 students all chose the same password.
- Every guess works against one account only. Precomputed tables of hashes become useless, and each account must be attacked separately.
Consider an LMS with 2,00,000 student accounts. Without salts, an attacker hashes each guess once and compares it against all 2,00,000 stored hashes at the same time. With unique salts, each guess has to be hashed separately for every account, so attacking everyone costs 2 lakh times more work. NIST's current guidance, SP 800-63B-4, requires salts of at least 32 bits (NIST); good libraries generate 128-bit salts automatically, so you rarely handle them yourself. Never reuse a salt, and never use the username or phone number as one.
Slow hashing and work factors
Password hashing algorithms have settings that control how expensive each hash is. Some also demand a lot of memory, which blunts attackers using graphics cards or custom chips, since those have plenty of computing power but limited fast memory per core.
- bcrypt has a single cost number. Each step up doubles the work, so cost 12 is four times slower than cost 10.
- Argon2id lets you set memory, number of passes and parallelism separately.
- scrypt has a combined CPU and memory cost, a block size and a parallelism setting.
- PBKDF2 has only an iteration count and uses little memory.
How slow is slow enough? OWASP's rule of thumb is that calculating a hash should take less than one second, and NIST says the cost should be as high as practical and increased over time. Apple's design for iPhone passcodes is a useful reference point: each attempt is calibrated to take about 80 milliseconds, and it has to run on the phone itself.
The exam-day trade-off
Slow hashing costs you server time as well. Suppose 20,000 students log in during the five minutes before a scheduled mock test, and each password check takes an illustrative 100 milliseconds of CPU. That is 2,000 CPU-seconds packed into 300 seconds: about seven processor cores doing nothing but checking passwords. The fix is not to weaken the hash. Instead:
- keep students signed in with sessions or refresh tokens, so most of them never type a password on exam day (see JWT vs sessions);
- add capacity for login servers before scheduled tests, as described in predictive scaling for exam-day traffic;
- rate-limit login attempts per account and per IP address, so attackers can't use your own servers to guess.
bcrypt, scrypt and Argon2id
| Algorithm | Background | Memory-hard? | OWASP minimum settings | Watch out for |
|---|---|---|---|---|
| Argon2id | Winner of the Password Hashing Competition in 2015; standardised as RFC 9106 in 2021 | Yes | 19 MiB of memory, 2 passes, parallelism 1 (or an equivalent trade-off) | Needs a maintained library; tune memory to your servers |
| scrypt | Published as RFC 7914 in 2016 | Yes | N = 2 to the power 17 (128 MiB), r = 8, p = 1 | Parameters are easy to get wrong |
| bcrypt | Described by Provos and Mazières in 1999, built on the Blowfish cipher's key setup | Slightly | Cost 10 or higher | Only the first 72 bytes of input count |
| PBKDF2 | NIST-approved key-derivation function | No | 600,000 iterations with HMAC-SHA256 | Weakest against graphics cards; use where FIPS compliance is required |
The settings come from OWASP's Password Storage Cheat Sheet (OWASP), which is updated as hardware improves. RFC 9106 itself suggests heavier options, such as 64 MiB of memory with three passes, if your servers can afford them. For a new system, choose Argon2id. bcrypt remains a sound choice where Argon2 isn't available, and PBKDF2 is the option when a compliance regime demands NIST-approved algorithms.
Libraries for these algorithms usually store the settings and salt alongside the hash, in a self-describing string. An Argon2id hash starts $argon2id$v=19$m=19456,t=2,p=1$ followed by the salt and the hash, and a bcrypt hash starts $2b$12$. That is what makes upgrades possible later.
Peppers
A pepper is a secret key applied on top of the salted hash, for example by running the password through an HMAC keyed with the pepper before hashing it. Unlike a salt, it is not stored in the database, so a database-only leak doesn't give attackers everything they need. NIST recommends this kind of additional keyed step, with the key held separately, ideally in a hardware security module; OWASP suggests a secrets vault or HSM.
Peppers have costs: rotating one is awkward, and losing it locks every user out. They are worth adding once the basics are right, not instead of them. One detail matters with bcrypt: if you pre-hash passwords, encode the result as hex or Base64 before passing it to bcrypt, because raw hash output can contain a zero byte that bcrypt treats as the end of the input.
Upgrading legacy hashes without forcing resets
Many older LMSs and coaching apps store MD5 or unsalted SHA-256 hashes. You can fix this without making every student reset their password:
- Record the algorithm with every hash, using a prefix or a separate column, so old and new formats can coexist.
- Wrap the old hashes now. Run each stored MD5 value through Argon2id or bcrypt with a new salt, and mark it as wrapped. OWASP describes this layering approach, and Django's documentation shows the same pattern, wrapping MD5 hashes inside PBKDF2 during a database migration (Django). The weak hashes disappear from the database immediately.
- Rehash cleanly at the next login. When a student logs in, you have the real password for a moment: verify it against the wrapped hash, then store a fresh Argon2id hash. Django does this automatically whenever the preferred algorithm or settings change.
- Retire the stragglers. After a fixed period, require a reset, by OTP for example, for accounts still on wrapped hashes.
A checklist for coaching apps and LMSs
- Argon2id at or above OWASP's minimum, or bcrypt with cost 10 or more.
- A unique random salt per password, generated by the library.
- Settings stored with each hash, and automatic rehashing when you raise them.
- Password rules from NIST SP 800-63B-4: at least 15 characters when the password is the only factor or 8 with multi-factor login, allow at least 64, check new passwords against a list of common and breached ones, and don't force periodic changes or composition rules.
- Rate limiting and alerts on repeated failed logins.
- Multi-factor login for staff, admin and faculty accounts, which can reach every student's data.
- Passwords never written to logs, analytics or error reports.
Many Indian coaching apps let students log in with a phone OTP. That moves the risk rather than removing it: admin panels and staff accounts still use passwords, and shared credentials remain a piracy problem, as our guide to stopping account sharing explains. For how hashing works in general, see what hashing is.
Key takeaways
- Fast hashes make offline guessing cheap; password hashes make every guess expensive.
- Salts force attackers to crack accounts one by one; peppers add a secret kept outside the database.
- Choose Argon2id for new systems, bcrypt where Argon2 isn't available, and PBKDF2 for strict FIPS settings.
- Plan login capacity for exam-day spikes rather than weakening the hash.
- Legacy MD5 or SHA hashes can be wrapped immediately and replaced at the next login.
Frequently asked questions
Is bcrypt secure?
Yes, bcrypt is still considered secure for password storage when used with a cost of 10 or higher, which is OWASP's minimum. Its limits are that it uses little memory compared with Argon2id and scrypt, so graphics cards attack it more efficiently, and it ignores anything beyond the first 72 bytes of a password. For new systems Argon2id is preferred, but moving an existing bcrypt system is rarely urgent.
Is bcrypt encryption or hashing?
bcrypt is a password hashing function. It borrows the key setup of the Blowfish cipher to make each hash deliberately expensive, but there is no key that can reverse it, and it doesn't produce ciphertext that anyone decrypts. To check a password, the system hashes the typed password with the stored salt and cost, then compares the result with the stored hash.
Is bcrypt better than SHA-256?
For passwords, yes. SHA-256 is designed to be fast, so a leaked table of SHA-256 password hashes can be attacked with billions of guesses per second on graphics cards. bcrypt is designed to be slow and adds a salt automatically. For other jobs, such as checking file integrity or signing data, SHA-256 is the right tool and bcrypt is not.
Is bcrypt reversible?
No. bcrypt is one-way: there is no key or procedure that turns a bcrypt hash back into the password. The only attack is to guess passwords and hash each guess with the same salt and cost, and bcrypt's slowness makes that expensive. Strong, unique passwords remain safe even if the hashes leak, while very common passwords can still be guessed.