Get unstuck

Hint 1: Pick the parameters once

Create a PasswordHasher with Argon2id at or above m=19456, t=2, p=1. verify() reads the parameters from the stored hash, so it checks old hashes too; your instance’s parameters define the policy for rehashing.

Hint 2: Treat the exception, and rehash

verify() raises VerifyMismatchError on a wrong password and InvalidHashError on an unusable string — both mean authenticated False. After a success, use check_needs_rehash to decide whether to return a fresh hash.

Reveal the reference patch and explanation
Fixedlogin.pyPython
from argon2 import PasswordHasher
from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError


def check_login(stored_hash, password):
    hasher = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
    try:
        hasher.verify(stored_hash, password)
    except (VerifyMismatchError, VerificationError, InvalidHashError):
        return {"authenticated": False, "new_hash": None}
    new_hash = hasher.hash(password) if hasher.check_needs_rehash(stored_hash) else None
    return {"authenticated": True, "new_hash": new_hash}

A PasswordHasher set to Argon2id m=19456, t=2, p=1 verifies the stored hash. A wrong password raises VerifyMismatchError, a bcrypt hash and a malformed string raise InvalidHashError, and all return {"authenticated": False, "new_hash": None}. On success, check_needs_rehash reports true for a hash whose parameters are below the instance’s (the m=512 hash) or whose variant differs (an Argon2i hash), so the function returns a fresh Argon2id hash at the policy minimum; a hash already at the policy strength needs no rehash and new_hash is None. argon2-cffi generates a random 16-byte salt per hash, so no salt handling is needed. The OWASP minimum (Argon2id m=19456, t=2, p=1) and the argon2-cffi defaults (RFC 9106 low-memory: m=65536, t=3, p=4) were both confirmed on 2026-10-11; either is acceptable, and parameters should be raised over time.

Compare it with your own fix, then rerun the tests. The editor is not changed when you open this explanation.

Inspect the actual test harness

The browser and the lab download run the same cases, including cases that catch common wrong fixes, such as comparing with one fixture value instead of the real rule. The download includes the complete test adapter and cryptographic fixtures.

Download the grader, python/cases.mjs
How the browser runs your Python

Your code runs in CPython 3.14.2 compiled to WebAssembly (Pyodide 314.0.7), in a Web Worker of its own. The test cases and the pass or fail verdict run in a second worker, which only sends your functions their inputs and reads back the values they return, so changing built-ins or the harness inside Python cannot change the result. All the cases together have a 8-second limit.

The first run downloads about 6.5 MB from this site, which your browser then keeps; later runs start in a second or two. Other pages never load it.

Available: the Python standard library, including sqlite3, ipaddress, pathlib, os.path and urllib.parse, and print(), whose output appears with your results. This lab also loads pycparser 3.0, cffi 2.0.0, argon2-cffi-bindings 25.1.0 and argon2-cffi 23.1.0: the exact wheels pinned by Pyodide 314.0.7, served from this site and checked against the lock file’s SHA-256 digests.

Not available: network access, subprocesses and threads, and other packages from PyPI. The lab download runs the same cases with your local python3 (3.12 or newer), once argon2-cffi==23.1.0 is installed (its README shows how).

Use the lesson in your own system

This lab models the verify-and-upgrade step; registration, lockout, rate limiting and breach checks are separate. It uses Argon2id by choice; scrypt, bcrypt (work factor ≥ 10, 72-byte input limit) and PBKDF2 (SHA-256 ≥ 600,000) are acceptable alternatives at the OWASP minimums. Rehash-on-login only upgrades accounts that log in; plan how to migrate or expire the rest. check_needs_rehash reports any parameter change, so a hasher weaker than a stored hash would downgrade it — keep the policy at or above the stored strength.

Your next task: In your own login path, do you rehash a password when the stored parameters fall below current policy, and do your parameters meet the current OWASP minimum?

OWASP Password Storage Cheat Sheet: Argon2id and upgrading hashes

Related guide: Password storage: hashing parameters and rehash on login