Verify and upgrade an Argon2 password hash on login

A login check verifies stored password hashes but never upgrades a weak or outdated one, so hashes created with tiny parameters or the wrong Argon2 variant stay that way forever. Repair check_login to verify with Argon2id at the policy minimum and rehash on a successful login.

Requires Node.js 22 or newer, which runs the tests, and Python 3.12 or newer, which runs your code (the command python3, or the program named in the PYTHON environment variable). This lab also needs argon2-cffi==23.1.0, the version the browser runs. Install it into a virtual environment: python3 -m venv .venv, then .venv/bin/pip install argon2-cffi==23.1.0, and run the tests with PYTHON=.venv/bin/python node --test exercise.test.mjs (on Debian or Ubuntu, install the python3-venv package first). No server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit login.py, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same grader with your code in Pyodide (CPython compiled to WebAssembly); locally it runs your code with python3. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. See what is not upgraded
Run the starter. It authenticates correctly, but a hash with tiny parameters and an Argon2i hash both pass with new_hash None: the weak hash is never replaced.

2. Verify with a policy-strength Argon2id hasher
Create a PasswordHasher with Argon2id and parameters at or above the OWASP minimum (m=19456, t=2, p=1). Use it to verify, and catch VerifyMismatchError and InvalidHashError so a wrong password or an unusable hash returns authenticated False instead of raising.

3. Rehash on login
When the password is correct and check_needs_rehash(stored_hash) is true — the stored parameters are below policy, or it is the wrong variant — compute a fresh hash and return it in new_hash for the caller to store. Return None when no upgrade is needed.

4. Fail closed
Confirm a wrong password, a bcrypt hash from an old system and a malformed string all return authenticated False without crashing, and that any new hash is Argon2id at the policy minimum and verifies the password.

SUPPLIED CONTRACT
check_login(stored_hash, password) returns {"authenticated": bool, "new_hash": str|None}. Build an argon2.PasswordHasher with Argon2id parameters at or above the OWASP minimum (m=19456 KiB, t=2, p=1; argon2-cffi’s defaults m=65536, t=3, p=4 also qualify). Call hasher.verify(stored_hash, password); on VerifyMismatchError, VerificationError or InvalidHashError return authenticated False with new_hash None. On success, return authenticated True, and when hasher.check_needs_rehash(stored_hash) is true, also return a fresh hasher.hash(password) in new_hash; otherwise new_hash is None. The library generates and stores a random salt for you.

REFERENCE
reference.py contains an explained solution: 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.

SCOPE
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.

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?

SOURCE
https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
