Cryptography
Password Hashing Libraries in 2026: 6 Options for Storing Credentials Safely
Store a slow verifier with a cost you can name. MD5 of the password is not a program, and Passlib 1.7.4 is leftover compatibility.
Expertise: Cryptography · Level: Intermediate · 6 min read
MD5 of the password is not a program. A homemade SHA loop does not become one because you iterated it 10000 times. Store a slow verifier with a cost you can name.
Passlib 1.7.4 shipped 8 October 2020. It is leftover compatibility, not a 2026 default. New logins belong in Argon2id or bcrypt, then rehash on next success.
When you can stop storing a password, passkeys are the other conversation. This shortlist is the verifier you still store.
Language-native Argon2, bcrypt in the platform library, and compatibility wrappers you should not start with split the choice. Pick a slow hash you can tune, then plan the rehash so old MD5 rows do not sit forever next to the new Argon2id verifier.
How we evaluated
We read first-party docs, source, licenses, and release notes, and we treat a marketing page as a claim, not as proof that two products do the same work.
We cared about whether the API is a modern KDF or a compatibility hash, whether you pick the call, whether memory or work factor is documented, and whether compare is the API they tell you to use. Passlib stays as legacy migration, not as a preferred new-project choice.
We read first-party docs, licenses, and release notes. Unless a write-up says otherwise, we did not run a paid tenant.
| Tool | Best for | What to check |
|---|---|---|
| argon2-cffi | Argon2id in Python you can actually call | Argon2id ยท MIT ยท you set memory and time |
| bcrypt | Known slow hash when Argon2 is not on the box | Work factor ยท Apache-2.0 ยท wide install base |
| libsodium | crypto_pwhash in the library you may already link | crypto_pwhash ยท ISC ยท you pick opslimit |
| Go x/crypto/bcrypt | bcrypt in a Go service you own | GenerateFromPassword ยท BSD-3-Clause ยท CompareHashAndPassword |
| passlib | Legacy Python hash compatibility, not a 2026 default | Legacy ยท BSD ยท last release 2020 |
| PHP password_hash | The language button on PHP | Language built-in ยท PHP License ยท password_needs_rehash |
argon2-cffi
Best for argon2id in Python you can actually call

The Python binding people import. Argon2id is the OWASP-shaped default if you still store a password.
You set memory and time, so a low cost is on you. It is not a passkey. For a Django or Flask app that still has a password column, this is the call.
Key features
- Argon2id helper
- MIT
- You set memory and time
- Well kept
Why we like it
A memory-hard verifier you can actually call is the Python default you can actually call.
Limits
You pick parameters. A low memory cost is theater. Not a passkey.
bcrypt
Best for known slow hash when Argon2 is not on the box

pyca bcrypt. Work factor you can name. Still acceptable when the runtime cannot do Argon2.
Honest fallback. Do not invent a homemade iterate-SHA. Memory-hard it is not. Tune the cost as hardware moves.
Key features
- Work factor
- Apache-2.0
- Wide install base
- You store the hash
Why we like it
A known slow hash beats a homemade loop when Argon2 is not on the box.
Limits
Memory-hard it is not. Tune the cost as hardware moves.
libsodium
Best for crypto_pwhash in the library you may already link

libsodium password hashing is Argon2-based in current docs. Same library as the boxed crypto you wanted anyway.
When sodium is already a dependency, do not add a third hash crate for fashion. Bindings differ. Read the current primitive name. It is not a user database.
Key features
- crypto_pwhash
- ISC
- Many language bindings
- You pick opslimit
Why we like it
If sodium is already linked, the password call should live there too.
Limits
Bindings differ. Read the current primitive name. Not a user database.
Go x/crypto/bcrypt
Best for bcrypt in a Go service you own

The import a Go login actually uses. Cost parameter. Compare is the API. Do not roll your own constant-time.
A Go app does not need a Python binding. Call this. bcrypt limits still apply. Argon2 in Go is a different module if you need it.
Key features
- GenerateFromPassword
- CompareHashAndPassword
- BSD-3-Clause
- Stdlib-adjacent
Why we like it
The stdlib-adjacent compare is the Go login. Homemade equality is the hole.
Limits
bcrypt limits. Cost is yours. Argon2 in Go is a different module if you need it.
passlib
Best for legacy Python hash compatibility, not a 2026 default

passlib 1.7.4 shipped 8 October 2020. It understands a menagerie of leftover schemes. That is a migration helper, not a library I would pick for a new login in 2026.
Current bcrypt releases have broken Passlib’s wrap. If you still need it, pin a known-good bcrypt, rehash to Argon2id or bcrypt on login, and plan the exit.
Key features
- Many schemes
- Context migrate helpers
- Docs on deprecation
- You still pick a default
Why we like it
Leftover hashes need a reader or the migration never ships. That is the only reason this row remains.
Limits
Unmaintained. Last release 2020. Compatibility problems with bcrypt 5. Do not start a new project here.
PHP password_hash
Best for the language button on PHP

password_hash and password_verify. Default follows PHP’s current PASSWORD_DEFAULT. Do not store a raw MD5 next to it.
A PHP app that still calls md5() is the hole this row exists to close. Read what DEFAULT is this year. Rehash on login. Not a passkey.
Key features
- Language built-in
- password_needs_rehash
- Current default documented
- No extra package
Why we like it
The language button is the PHP login. md5() next to it is the leftover.
Limits
Read what DEFAULT is this year. Rehash on login. Not a passkey.
What we left out
- bcrypt.js. People want the npm they already import in a Node login. This page already covers bcrypt and language buttons, and a third bcrypt fails the focused-shortlist check.
- scrypt bindings. Teams want another memory-hard KDF they saw in an older guide. Argon2id already covers that work, and current guidance points there first.
Questions before you buy
Ask these before the quote. A product that cannot answer them is selling a different control.
- Does a new user land in Argon2id or bcrypt with a cost we can name?
- Is compare the API we call, or did someone roll a homemade check?
- Are we migrating leftover schemes, or can we set a single default today?
Call Argon2id and name the memory if you can. Name the bcrypt work factor if that is what the runtime does well. Leave Passlib after the table is one default.
FAQs
Argon2 or bcrypt?
Argon2id if you can. bcrypt if that is what the runtime already does well. Name the cost.
Should I encrypt the hash too?
A pepper can help a stolen table. It is not a substitute for a slow KDF.
Do passkeys retire this list?
For those users, yes. Until then you still store a verifier. Open passkeys.
Is this a scored bake-off?
No.