mrkeyoor.com_
Tue 22 Sept 00:43 UTC
PyPISecurityupdated 21 Sept 2026

passlib review

Passlib 1.7.4 presents more than 30 password-hash formats through common hash, verify, identify, and upgrade operations. CryptContext can read several historical formats, write new passwords with the preferred scheme, and return a replacement hash after a successful login. That makes it most useful during long account migrations. Our Python 3.12 install was pure Python and imported quickly, but the current PyPI release has not changed since October 2020 and declares no Python version range.

Verdict

Passlib 1.7.4 installed in 0.2 seconds and used 3 MB in our sandbox, with zero audit findings but no py.typed marker. Keep it for controlled multi-format migrations; do not choose its unchanged 2020 release for a new single-scheme password store.

We installed it

Lab card: what happened when we installed passlibScreenshot of passlib documentation
Install✓ · 0.2s1 package on disk · 3 MB
Importimport passlib in 0.07s · pure Python
Known vulns0(pip-audit)

Answers from our run

Does passlib install cleanly?

Yes. In a fresh container with an empty cache, pip install passlib finished in 0.2s, leaving 1 package and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does passlib need to run?

Python 3.x, and nothing compiled: it is pure Python. In our run import passlib succeeded in 0.07s.

passlib or pwdlib: which should you use?

pwdlib: Use it for a maintained password API centered on current schemes and automatic upgrades. Passlib 1.7.4 installed in 0.2 seconds and used 3 MB in our sandbox, with zero audit findings but no py.typed marker.

When should you not use passlib?

You are designing a new password store. pwdlib or argon2-cffi has a narrower modern purpose and newer releases.

API stability5/5CryptContext and handler methods such as hash, verify, identify, needs_update, and verify_and_update have kept the same general shapes for years. Existing login migrations therefore see little churn. The absence of releases since 2020 contributes to that calm and also means the API has not adopted current Python packaging and typing conventions, so stability is not evidence of current platform support.
Docs4/5The stable documentation returns 200 and covers CryptContext policy, individual schemes, Apache password files, TOTP, optional backends, migration, bcrypt's 72-byte input rule, and login-time upgrades. It describes the 1.7.4 behavior well. It cannot supply compatibility evidence for Python interpreters and backend releases published during the following six years, leaving that work to application tests.
Maintenance1/5PyPI lists 1.7.4 from 2020-10-08 and no later release. The empty Requires-Python field and release notes that still discuss Python 2.6 show how old the published compatibility contract is. Our pip-audit run found zero known advisories, but an advisory database does not test current interpreters, optional native backends, packaging tools, or password-policy behavior.
Ecosystem4/5The stored registry count is 9,793,810 weekly downloads. One CryptContext can accept Unix, LDAP, Django, PBKDF2, bcrypt, Argon2, and other historical values while selecting one replacement policy. That remains useful in old account systems and migrations. New applications increasingly use one focused scheme package, so the installed base should not be mistaken for a greenfield recommendation.

Use it if

  • One account table contains several legacy hash formats that must remain verifiable during migration.
  • A successful login should upgrade an obsolete scheme or cost and return the replacement for storage.
  • An import job needs to classify configured Django, Unix, LDAP, PBKDF2, bcrypt, or Argon2 encodings.
  • A mature application already has a tested CryptContext whose replacement would create migration risk.
Skip it if

Setup reality

We installed passlib 1.7.4 in a fresh Python 3.12 Bookworm sandbox in 0.2 seconds. One installed package used 3 MB. pip-audit reported zero known vulnerabilities. Our metadata inspection counted six direct dependencies, found pure Python code, an unspecified Python requirement, a BSD license, and no py.typed marker. import passlib completed in 0.07 seconds.

The base import does not prove a chosen handler can hash. Argon2 needs argon2-cffi, bcrypt uses its bcrypt backend, and encrypted TOTP storage needs cryptography through the relevant extra. Run one hash and one verification in deployment checks. The 2020 release predates several current Python and backend versions, so pin and test the whole resolved environment.

CryptContext order defines policy: the first active scheme writes new hashes, and deprecated auto marks older configured schemes for replacement. verify_and_update returns validity plus an optional new hash but never saves it. Persist the replacement inside the authenticated login transaction, account for simultaneous logins, and expose the same public failure for an unknown user and a wrong password.

identify only recognizes handlers configured on that context. None may mean damage or a valid unconfigured scheme, so quarantine those records during imports. Standard bcrypt processes only its first 72 input bytes; truncate_error can reject longer inputs, while Passlib's bcrypt_sha256 prehash format is not interoperable with plain bcrypt tools. Benchmark cost parameters on deployment hardware before raising them.

Patterns

Write Argon2 while accepting one legacy scheme configure-hash-policy

passwords = CryptContext(schemes=['argon2', 'pbkdf2_sha256'], deprecated='auto')
stored = passwords.hash(plain_password)

The first non-deprecated scheme receives new hashes. Argon2 also requires its optional backend.

Check plaintext against a stored value verify-password

if not passwords.verify(submitted_password, user.password_hash):
    raise InvalidCredentials()

Plaintext is the first argument. Handle a malformed stored value separately in internal logs.

Persist a replacement after authentication upgrade-on-login

valid, replacement = passwords.verify_and_update(password, user.password_hash)
if not valid: raise InvalidCredentials()
if replacement is not None:
    user.password_hash = replacement
    session.commit()

verify_and_update returns the new hash but never writes it to the user record.

Classify a configured hash identify-format

scheme = passwords.identify(stored_hash)
if scheme is None:
    quarantine_record(stored_hash)

None covers both damaged text and valid formats absent from this context, so migration code should retain the ambiguity.

Flag an outdated stored policy check-upgrade-needed

if passwords.needs_update(user.password_hash):
    schedule_rehash_at_next_login(user.id)

A new hash still needs the user's plaintext, so the usual replacement point is the next successful login.

Stop bcrypt from truncating input reject-long-bcrypt

strict_bcrypt = bcrypt.using(rounds=12, truncate_error=True)
stored = strict_bcrypt.hash(password)

bcrypt's 72-byte limit otherwise discards the remaining input; benchmark the example cost on your hardware.

Spend hash work for a missing account hide-user-enumeration

user = find_user(username)
if user is None:
    passwords.dummy_verify()
    raise InvalidCredentials()

dummy_verify reduces the timing difference between an unknown account and a wrong password for a real one.

Save an htpasswd credential update-htpasswd

users = HtpasswdFile('/etc/nginx/private.htpasswd', new=False)
users.set_password('ada', new_password)
users.save()

save rewrites the file; serialize competing writers and keep restrictive permissions on the hash file.

Alternatives

PackageRegistryPick it when
pwdlibPyPIUse it for a maintained password API centered on current schemes and automatic upgrades.
argon2-cffiPyPIUse it directly when Argon2id is the only format and legacy identification is unnecessary.
bcryptPyPIUse it when an existing policy or database fixes bcrypt as the sole hash format.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.