The UnknownHashError in Passlib means that the hashing algorithm specified in your user’s stored password hash is not recognized by the current version of Passlib you’re running. This typically happens when you upgrade Passlib and it deprecates or removes support for older hashing algorithms.
Here’s how to fix it, covering the most common culprits:
1. Outdated Passlib Version: You might be running an older version of Passlib that doesn’t support the algorithm your database is using. This is especially common if you’re migrating an older application.
- Diagnosis: Check your
requirements.txtorPipfilefor the Passlib version. Then, check the Passlib release notes or documentation for algorithm support in that version and compare it to your stored hashes. - Fix: Upgrade Passlib to the latest version:
(Replacepip install --upgrade passlib[bcrypt][bcrypt]with your actual desired hashing context, e.g.,[argon2],[scrypt], or leave it blank if you’re not specifying a context). - Why it works: Newer versions of Passlib include support for algorithms that might have been removed or are simply not loaded by default in older versions.
2. Missing Algorithm Dependency: Many hashing algorithms in Passlib are optional and require separate installation. If you upgraded Passlib but didn’t install the specific dependency for the algorithm you’re using, you’ll get this error.
- Diagnosis: Examine your stored password hashes. They will usually start with a specific prefix indicating the algorithm (e.g.,
$2b$for bcrypt,$$argon2i$$for Argon2). Then, check Passlib’s documentation for the required installation for that algorithm. - Fix: Install the missing dependency. For example, if your hashes are Argon2:
If they are bcrypt:pip install argon2-cffi
If they are scrypt:pip install bcryptpip install scrypt - Why it works: Passlib acts as a wrapper. It needs the underlying C libraries or Python packages to perform the actual hashing and verification for certain algorithms.
3. Incorrect Hashing Context Configuration: Your application might be configured to use a specific set of algorithms (a "context"), but the stored hash uses an algorithm not included in that context.
- Diagnosis: Look at your Passlib configuration. It might be a
passlib.context.CryptContextobject. Check theschemesparameter. - Fix: Ensure the algorithm used in your stored hashes is listed in your
CryptContextconfiguration. For example, if your stored hashes are bcrypt and your context only listsdjango_pbkdf2_sha1, you need to addbcrypt:from passlib.context import CryptContext # Assuming your stored hashes are bcrypt and you want to support pbkdf2 too pwd_context = CryptContext(schemes=["bcrypt", "pbkdf2_sha1"], deprecated="auto") - Why it works:
CryptContextdictates which hashing algorithms Passlib will attempt to use for verification. If an algorithm isn’t in the list, Passlib won’t even try to use it, leading toUnknownHashError.
4. Algorithm Name Mismatch in Configuration: A typo or an incorrect identifier for the hashing algorithm in your Passlib configuration can cause this.
- Diagnosis: Carefully compare the algorithm identifier in your stored hash (e.g.,
bcrypt) with the string used in yourCryptContextconfiguration. - Fix: Correct the algorithm name in your
CryptContext. For instance, if you accidentally wrote"bcyrpt"instead of"bcrypt":# Incorrect # pwd_context = CryptContext(schemes=["bcyrpt"], deprecated="auto") # Correct pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - Why it works: Passlib uses exact string matching to identify and load hashing schemes. Any discrepancy prevents it from finding the correct handler.
5. Deprecated Algorithm Not Marked for Deprecation: If an algorithm has been completely removed from Passlib in a very recent version, and you haven’t explicitly configured Passlib to be aware of its deprecation, it might raise this error.
- Diagnosis: Check the Passlib documentation for the specific algorithm in your hash. See if it’s listed as removed or entirely unsupported in the version you’re using.
- Fix: You have two main options:
- Re-hash all passwords: This is the most secure long-term solution. Iterate through your users, log them in with their old password (which might require a temporary fallback in your
CryptContext), and then immediately re-hash and update their password using your new preferred hashing algorithm.# Example: In your login endpoint after successful authentication user.password = pwd_context.hash(new_password_from_user_input) db.session.commit() - Add a fallback scheme: If re-hashing isn’t immediately feasible, you can add the deprecated scheme back into your
CryptContextfor backward compatibility, but mark it as deprecated so new users get migrated.from passlib.context import CryptContext from passlib.hash import sha256_crypt # Example of a potentially deprecated scheme # Add the deprecated scheme back, but ensure it's not the primary one pwd_context = CryptContext(schemes=[ "bcrypt", # Your preferred new scheme "sha256_crypt", # The old, now deprecated scheme "fallback" # Use if you have other legacy hashes ], deprecated=["sha256_crypt"], fallback=sha256_crypt) # Provide a concrete instance for fallback
- Re-hash all passwords: This is the most secure long-term solution. Iterate through your users, log them in with their old password (which might require a temporary fallback in your
- Why it works: By explicitly listing the deprecated scheme and marking it in
deprecated, you tell Passlib to attempt verification with it, while also signaling that it’s old and should be updated. Thefallbackparameter ensures that if the primary schemes fail, Passlib tries the specified fallback instance.
6. Corrupted Hash String: In rare cases, the hash string stored in your database might be corrupted (e.g., truncated, invalid characters).
- Diagnosis: Manually inspect the hash string from your database for your user. Does it look like a valid hash string for any known algorithm? Does it have the correct length and character set?
- Fix: This is tricky. If the hash is truly corrupted, you cannot recover the original password. You’ll need to force a password reset for that user. The user will have to go through the "forgot password" flow, and their password will be re-hashed with your current scheme upon successful reset.
- Why it works: A corrupted hash string is unparseable by any hashing algorithm, so Passlib can’t even begin to attempt verification. A reset forces a new, valid hash to be generated.
The next error you’ll likely encounter after resolving UnknownHashError is a ValueError or TypeError if the password verification itself fails due to an incorrect password, or a ValidationError if you’re using Pydantic models for input validation.