Wait until each user next signs in, check the stored hash parameters, and if they are below your target, re-hash the password you just verified with the new settings and save it. OWASP calls this the most common approach. It upgrades active users with no reset emails and no downtime.
Why you cannot just recompute
You never stored the passwords, only hashes. A hash cannot be converted into a stronger hash of the same password. The only moment you hold the plaintext is during a successful login, so that is when the upgrade happens.
What the target settings are
OWASP's Password Storage Cheat Sheet gives these minimums.
| Algorithm | Minimum setting from OWASP | Note |
|---|---|---|
| Argon2id | 19 MiB memory, 2 iterations, 1 parallelism | Preferred |
| scrypt | N of 2 to the 17 (128 MiB), r of 8, p of 1 | If Argon2id is unavailable |
| bcrypt | Work factor of at least 10 | Legacy systems, 72 byte limit |
| PBKDF2 | HMAC-SHA-256 with 600,000 iterations | When FIPS-140 compliance is needed |
Treat these as floors and check the current page when you plan a change.
The login time upgrade, step by step
- Store the algorithm name and parameters with every hash. Most modern formats already embed them in the string.
- On login, verify the password against the stored hash as usual.
- If verification succeeds, compare the stored parameters with your current policy.
- If they are weaker, hash the plaintext with the new settings and overwrite the old value in the same transaction as the login.
- Log the upgrade as an event, without the password or hash.
- Track how many accounts remain on the old settings.
Many password libraries expose a function that tells you whether a hash needs rehashing, which saves you from writing the comparison yourself.
Changing algorithm, not just cost
Moving from a weak or fast hash to a proper one works the same way, with one trick. If the old values were plain fast hashes, you can wrap them right away without waiting for logins: feed the old hash into the new password hash and record that the row uses the wrapped scheme. On the next login, verify through the wrapped path, then store a clean new hash. This protects dormant accounts from the day you deploy.
What about accounts that never log in
Set a deadline. After it, expire the old hashes for inactive accounts and require a reset on their next visit. Decide this with support and security together, and tell users why.
Mistakes to avoid
- Upgrading before the password is verified.
- Writing the new hash in a separate request from the login, so a failure leaves a mix.
- Keeping the old hash column around after the migration is done.
- Silently capping password length at a value below what your new hash accepts.
Safeguard can generate an SBOM and scan your dependencies, so outdated cryptography libraries with known advisories show up in one inventory, and policies can gate a build on them. A free scan is at gold.safeguard.sh/scan.
Quick check
Do I need to tell users?
Not for a silent re-hash at login, since nothing changes for them. Do tell them if you require a reset.
Should I pick bcrypt or Argon2id for the new hash?
OWASP prefers Argon2id and lists bcrypt for legacy systems. If bcrypt is what you have, raise the work factor and plan a move.
Sources
Last reviewed: 19 October 2026.