Last reviewed: November 3, 2026 (Asia/Shanghai); sources fetched November 2 UTC.

Direct answer

Start a password-hashing review for coding-agent patches by writing the accepted storage profile. Require a password-specific hashing scheme, a fresh random salt, a cost benchmarked on the target service, complete password verification, and a migration path for older hashes. Follow a successful verification through any replacement write. Test that the migration cannot overwrite a concurrent password change or downgrade a hash during a rolling deployment.

OWASP’s Password Storage Cheat Sheet recommends Argon2id and publishes minimum configurations. Within its scope, NIST SP 800-63B-4 requires salted password hashing resistant to offline attack and recommends recording scheme and cost information for migration. The argon2-cffi API supplies concrete verification and rehash operations. These references support the workflow below; the test matrix is proposed acceptance criteria, with no application execution or benchmark claimed.

This workflow concerns centrally verified passwords. API keys, reset grants, single sign-on assertions, and local device unlock secrets have different contracts. A strong hash also does not repair an account-enumeration bug, a missing rate limit, or a session that remains active after a credential change.

Who this is for

Use this guide when an agent changes registration, login, password-change, account-recovery, user migration, credential import, or authentication middleware. It is especially useful when a patch replaces a fast digest with a password hasher, changes Argon2 parameters, adds “rehash on login,” or moves verification into a worker or shared helper.

The example assumes a server stores an encoded password hash and a credential version per account. Match the library, database, worker model, and password policy in the repository. For regulated deployments, have the security owner select an approved scheme and implementation; OWASP’s Argon2id recommendation alone does not establish FIPS compliance.

Key takeaways

  • Define the algorithm, variant, parameters, input handling, and migration rules before reviewing code.
  • Prefer a maintained password-hashing library; do not hand-roll salts, encodings, or comparison logic.
  • Let the library generate a fresh random salt for each new hash and store the encoded result as opaque data.
  • Benchmark verification under realistic concurrency. A cost that is safe for one request can exhaust a login fleet.
  • Treat a successful verification and a rehash as a credential transition with race and failure cases.
  • Check password blocklists and failed-attempt rate limits alongside storage changes.
  • Keep passwords, hashes, peppers, and raw verifier errors out of ordinary logs and agent transcripts.

Contract details to verify

Name the stored-hash profile

Write the profile in the review record. For a new deployment, an example profile is Argon2id with a configured memory cost, time cost, parallelism, output length, and random salt. OWASP lists Argon2id minimum configurations, including 19 MiB of memory, two iterations, and one lane, but it also says to tune the work factor for the server. Treat those values as a floor from the reference, not a production benchmark.

Record how the profile is encoded and how a verifier recognizes it. A standard encoded string normally carries the algorithm, version, parameters, salt, and derived value. NIST says a reference to the hashing scheme and cost factor should be stored with each password so the service can migrate algorithms and work factors. Do not split those fields into an ad hoc format unless the library and migration code have an explicit, tested contract.

For a legacy system, list every accepted format and its retirement condition. Identify the format, verify it with its maintained implementation, and migrate after successful verification according to the written policy. Never try another algorithm because parsing failed. Keep hash import and administrative write paths under the same format policy.

Keep secret input and salt handling explicit

Hash the complete submitted password. NIST requires full-password verification, recommends permitting a maximum length of at least 64 characters, and recommends NFC normalization when Unicode is accepted. Do not silently truncate at a byte count inherited from an old algorithm. Document encoding and normalization consistently across registration, change, and verification. Changing those rules during migration can strand existing users or bind a new hash to a different input.

A salt is stored with the hash. OWASP explains that random salts prevent reusable precomputed lookups and separate accounts using the same password. Let the library create and encode it: argon2-cffi’s PasswordHasher.hash() securely creates a random salt when none is supplied. Reject patches that substitute a client-selected value or a fixed salt shared by accounts.

A pepper is a separate control. OWASP describes it as a shared secret stored outside the password database, such as in a secrets vault or HSM. NIST recommends an additional keyed operation with a separately held verifier secret. If adopting peppering, define rotation and forced-reset behavior: OWASP warns that replacing the pepper requires knowledge of users’ passwords. Keep it out of the encoded hash and repository constants.

Verify before granting and upgrade only after success

For this example, the login policy rejects a detected concurrent credential change and withholds a full authenticated session if the required upgrade cannot complete. Trace the handler in this order:

  1. Apply request-size and account lookup limits before invoking the expensive verifier.
  2. Load the encoded hash and credential version from the authoritative account record; confirm the format and parameters are allowed.
  3. Verify the submitted password with the maintained library. Keep mismatch, invalid-record, and operational-failure categories internally; grant no access on any failure.
  4. Check account status and determine whether the approved migration policy permits a replacement.
  5. If needed, hash the same submitted password with the destination profile and conditionally persist it against the observed credential version and old hash.
  6. Complete the application’s credential-version and account-policy checks before issuing the intended session or MFA challenge. A losing conditional update must not fall through to full login.

argon2-cffi documents that verify() returns true on success and raises exceptions for mismatches, invalid hashes, and other failures. A branch that checks only for a false return is wrong for this API. Handle the documented exceptions without a catch-and-accept fallback, and keep raw error detail out of client responses and routine logs.

check_needs_rehash() detects a difference from the configured hasher parameters. It does not rank two profiles by security or authorize every replacement. A worker with older settings can otherwise replace a newer hash during a rolling deployment. Define approved migration directions, keep worker configuration consistent, and test both worker versions. The library recommends checking after successful authentication, when the password is available.

Use an atomic conditional update against the observed account version and hash. If a password reset commits first, the paused login’s replacement must affect zero rows and follow the losing-update policy. Protect credential writes and session issuance with the application’s reviewed concurrency and revocation design; a successful compare-and-swap does not alone settle every login/reset race. Never queue plaintext passwords for background rehashing. No library rehash helper supplies database atomicity.

Bound online work and policy inputs

The argon2-cffi API warns that the caller must control the encoded hash; the library does not apply a general validation policy to it. Verification reads the stored hash’s parameters, so a low-cost destination hasher does not bound the work needed to check an imported record. Bound encoded input size and allow only reviewed variants, versions, and parameter ranges before expensive verification. Use supported parsing helpers and reject excessive or unknown values without trying them.

OWASP warns that an excessive work factor can enable denial of service. Benchmark on the target CPU and memory class under concurrent successes and failures, including login-time rehashing, which adds another expensive operation. Measure latency, memory pressure, queue depth, and active verifiers. Bound admission and execution capacity as well as request duration. A request timeout must not release a capacity slot while native hashing continues; verify the runtime’s actual cancellation behavior.

NIST requires account-level failed-attempt rate limiting and a blocklist of common, expected, or compromised passwords. Its password-verifier rules prohibit other composition requirements. Review whole-password blocklist checks at establishment and change, alongside support for password managers, autofill, and paste. A character-class regex cannot substitute for those controls. Bound input size without silently truncating an accepted password.

Account status and MFA remain separate checks. A correct password must not grant full access to a disabled account or an account that still needs MFA. Recovery grants also need the site’s password-reset review workflow .

Happy-path operator workflow

Freeze the agent revision, password policy version, hashing-library version, and configured profile. Use disposable accounts and a test secret that never appears in shared evidence. Create an account, inspect only the encoded-hash metadata, and verify that the stored value contains no plaintext. Sign in through the real route and confirm that the intended principal and session are created once.

For an upgrade, seed an older valid hash. Authenticate with the test secret, verify the destination profile, and confirm a second login leaves the stored value unchanged. Then pause a login after verification, commit a password change elsewhere, and release it. Require the newer credential to remain and the stale attempt to withhold full login under the example policy.

Record the profile revision, library version, result class, account-state transition, verifier duration bucket, and whether an upgrade was attempted. Keep the secret, complete hash, pepper, authorization header, and raw exception out of the record. If the test uses a real worker pool, preserve the worker and database transaction identifiers without retaining credential material.

Prove the boundary with a hash-review matrix

Run these cases through the actual registration, login, and password-change paths. Use an injectable clock only for surrounding account-expiry rules; password-hash cost itself is not a wall-clock expiry.

FixtureRequired observation under the example policy
New account with a compliant passwordOne encoded current-profile hash; no plaintext or reusable client salt
Same password hashed twice, including for different accountsFresh salts and distinct encoded values
Correct passwordOne authenticated principal and one intended session transition
Wrong passwordNo principal, protected handler, or credential replacement; expected attempt counters may change
Empty, oversized, malformed, or invalidly encoded stored hashBounded controlled failure; no fallback to a fast digest
Unknown format, variant, version, or excessive parametersRejected before costly verification; no algorithm guessing
Long and Unicode passwordEntire value is handled consistently at registration and login
Blocklisted candidateRejected before account creation or password replacement; generic safe explanation
Repeated failed attemptsAccount-level attempt limit and configured source controls engage without disclosing account existence
Valid legacy hashOne successful verification, then one current-profile upgrade
Two concurrent upgrades, including mixed worker profilesNo downgrade; losing update follows the written policy
Login paused after verification while a reset commitsReset survives; stale login does not overwrite it or gain full access
Upgrade write failure or lost commit acknowledgmentNo full login; authoritative record remains a complete valid credential
Disabled or MFA-pending account with a correct passwordNo full-access session; intended denial or MFA challenge
Password change with an active sessionSession and reauthentication behavior matches the written policy

For negative cases, count session creation, credential writes, and queued business work. Failed-attempt counters and sanitized security events can be expected rejection effects; distinguish them from protected actions. A 401 response alone cannot prove that the handler did not create a session first. Coordinate concurrent requests at the database boundary rather than relying on sleeps, then inspect durable state after both finish.

Error-path operator workflow

Classify a malformed stored hash as a credential-store integrity problem. Reject it without trying SHA-256, plaintext comparison, or an unconfigured algorithm. Return the normal authentication failure externally and alert through a sanitized operational channel. Preserve the account and transaction identifiers needed to repair the record without logging the hash. Include an unknown-account fixture in the integration suite and compare response shape and timing; early lookup failures must not expose account existence.

On execution-capacity exhaustion or verifier failure, withhold access and use a controlled response. Keep the slot owned until hashing actually finishes or terminates; returning an HTTP timeout is not evidence that computation stopped. Avoid automatic repeated verification. A confirmed rollback must leave the prior credential intact. A lost commit acknowledgment may mean the replacement persisted: reconcile the authoritative record before retrying, and never restore the old hash blindly. Under this example’s policy, withhold full login until that outcome is resolved. Verify that no failure saves a partial or empty hash.

When an old algorithm must be retired, publish a migration plan with an account cohort, a rehash deadline, and a recovery path for users who never return. Bulk offline rehashing without the cleartext password is not equivalent to a direct new hash. Do not wrap an old fast hash inside a new password hash and call the result a completed migration unless the security owner has accepted that temporary tradeoff and a direct replacement occurs at the next successful login.

Failure modes

The patch stores a fast digest. A digest can be correct for file integrity and still be unsuitable for passwords. Require a password-specific, adaptive scheme and inspect the actual verifier configuration.

The salt is constant or caller-controlled. Distinct accounts become easier to compare, and a user can influence the stored format. Let the maintained library generate a random salt.

The cost is chosen from a developer laptop. Production concurrency can turn verification into an outage. Benchmark the target worker class and add rate limits and deadlines.

Rehash-on-login runs before verification. An attacker can cause writes or expensive work with an incorrect password. Upgrade only after a successful, policy-compliant verification.

A migration fallback accepts whatever parser succeeds. An attacker-controlled marker can steer the verifier to a weaker algorithm. Enumerate supported formats in configuration and reject unknown ones.

A password change updates the hash but leaves authority ambiguous. Existing sessions, refresh credentials, or MFA state may remain valid. Review the session and credential-revocation contract separately; the session-fixation review workflow covers identifier transitions.

FAQ

Is OWASP’s Argon2id minimum the right production setting?

Benchmark the selected memory, iteration, and parallelism values on the target service under concurrent load. Record the profile and its approved migration directions. A deployment’s compliance requirements may also constrain the scheme and implementation.

Should an application hash a password twice with different algorithms?

A temporary compatibility layer can be an explicit migration technique, but it does not replace a direct modern hash once the cleartext password is available. Keep the legacy branch narrow, record its retirement condition, and avoid presenting layered hashes as equivalent to a current profile.

Can a pepper replace a salt?

No. A salt is unique and stored with each hash; a pepper is shared, secret, and stored separately. They address different threats and have different rotation costs.

Does a successful hash verification mean the user is authenticated?

It proves that the submitted secret matches the stored verifier under the selected algorithm. Account status, MFA, session issuance, authorization, and revocation policy still have to pass.

Does check_needs_rehash() always request a stronger hash?

No. It detects whether the stored hash differs from the configured parameters. An older worker can see a newer hash as different too. Apply the migration policy before replacing it, and test mixed-version deployment behavior.

Reader next step

Choose one agent-written authentication change and write its hash profile, accepted legacy formats, upgrade policy, and account-state rules. Add a real wrong-password case, a seeded legacy-hash upgrade, a malformed-hash case, a rate-limit case, and a coordinated concurrent upgrade. Approve the patch only when the complete password reaches a maintained verifier, the stored value is salted and adaptive, and no rejected attempt creates identity or mutates credentials.

Sources checked

  • OWASP: Password Storage Cheat Sheet — fetched November 2, 2026 UTC. Supports adaptive password hashing, random salts, Argon2id minimum configurations, work-factor tuning, pepper handling, and migration guidance.
  • NIST: SP 800-63B-4, Password Verifiers — fetched November 2, 2026 UTC. Supports full-password handling, minimum length and maximum-length guidance, blocklists, rate limiting, salted resistant storage, cost-factor tuning, and storing scheme metadata.
  • argon2-cffi 25.1.0 API Reference — fetched November 2, 2026 UTC. Supports the Argon2id PasswordHasher, random salts, verification result and exception behavior, parameter profiles, and check_needs_rehash() upgrade checks.

These maintained primary references support an evergreen patch-review task. The profile, race tests, operational workflow, and failure handling are engineering recommendations derived from the sources. No application deployment, benchmark, or test result is claimed.