Passwords need one-way, slow storage
A password database should not contain values that can be reversed into the original password. Encryption is not enough because a database administrator or attacker with the key could decrypt every password. Password hashing deliberately makes each guess expensive and uses a unique salt so two users with the same password do not produce the same stored value. The objective is to make offline guessing costly while keeping normal login acceptable.
Use a password-hashing algorithm designed for credentials, such as Argon2id, rather than a fast general-purpose digest. SHA-256, even with a salt, is too efficient for an attacker with specialized hardware. A slow hash is not a substitute for MFA, breach detection, or rate limiting, but it reduces the impact when a credential store is exposed.
Tune memory, time, and parallelism from measurements
Argon2id exposes memory cost, time cost, and parallelism. Higher values make guessing harder but consume more resources during login and password creation. Benchmark on the smallest production instance and under expected concurrent authentication traffic. Set a maximum verification duration and monitor it. A parameter that is safe on a developer laptop can cause a login outage when a server receives a burst of password attempts.
Store the algorithm and parameters in the encoded hash string so the verifier knows how it was created. When a user logs in successfully with an older, weaker parameter set, rehash the password with the current policy and replace the stored value. Do not rehash on every request or create a background job that needs plaintext passwords. A successful verification is the only time the plaintext is available.
Generate and protect salts correctly
The password library should generate a cryptographically random salt for every password. Do not use a username, email, timestamp, or application-wide salt. A unique salt prevents precomputed tables and makes identical passwords appear different. The salt is not secret and can be stored with the hash. The password and hash must be handled in memory carefully, but application languages may not guarantee immediate zeroing.
Use the library's constant-time verification path and avoid comparing derived strings manually. Do not log password errors that include the input, hash, or salt. A login failure response should not reveal whether the account exists unless the product consciously accepts that enumeration risk. Rate-limit attempts and monitor unusual failures without storing sensitive credentials in telemetry.
Migrate legacy hashes without a risky reset
Legacy systems may contain bcrypt, scrypt, PBKDF2, or even unsalted fast hashes. Identify the format and verify it with a dedicated implementation before deciding how to migrate. A staged migration can authenticate a legacy hash once, then rehash with Argon2id after a successful login. Accounts that never return can be reset through a secure recovery flow or migrated by policy after a defined deadline.
Do not accept a weak hash indefinitely because migration is inconvenient. Mark the algorithm version, monitor the remaining population, and notify users when action is needed. If the old format has no salt or uses a reversible encryption key that was exposed, treat it as compromised and force a credential reset. Preserve sessions carefully so a migration does not create an unintended account takeover path.
Protect the login endpoint too
A strong hash can still be abused through online guessing. Apply rate limits per account, IP, device signal, and authentication route, with a policy that does not lock a victim out forever when an attacker knows their email. Add progressive delays or require a second factor after suspicious attempts. Return consistent messages for unknown and wrong-password cases when account enumeration matters.
Use secure session issuance after a successful hash verification. Rotate the session identifier after login, set appropriate cookie attributes, and invalidate sessions after a password change according to the account recovery policy. Do not store password-reset tokens as reversible passwords in a table. Store a hash or a short-lived signed value and mark it single-use.
Handle password recovery as authentication
A password-reset flow is an alternate login path and deserves the same threat model. Generate high-entropy, short-lived, single-use tokens, deliver them through a verified channel, and avoid putting long-lived secrets into analytics or referrer-visible URLs. Consume the token atomically so two requests cannot both reset the password. Notify the account through a channel that does not reveal whether a user exists.
After a reset, revoke sessions and refresh credentials as the product requires, review MFA enrollment, and provide a clear security notification. Do not let a support override bypass identity verification. The safest recovery path may be slower than normal login because it is used precisely when the primary credential is unavailable.
Test and review the policy periodically
Test correct and incorrect passwords, malformed hashes, legacy migration, concurrent verification, rate limits, timing behavior, reset token reuse, and session invalidation. Benchmark memory and time costs after runtime or instance changes. Scan logs and crash reports for accidental credential values. Review the algorithm and parameters against current security guidance and the actual threat model rather than leaving the initial choice untouched for years.
A password system is a lifecycle: generate a salted memory-hard hash, verify safely, migrate parameters, rate-limit online guessing, secure recovery, and revoke sessions when risk changes. Argon2id is an important primitive, but the surrounding account and recovery flows determine whether the user is actually protected.
Record the policy without recording the password
Store the encoded algorithm and parameters with the hash, plus a policy version in safe account metadata if migration reporting needs it. Never store a plaintext password in a job, event, support record, or analytics payload. When a hash is upgraded after login, make the replacement conditional on the version that was verified so a concurrent password change cannot be overwritten.
Benchmark verification under an attack-like rate as well as normal login traffic. A memory-hard setting can protect offline guesses while exhausting a small instance when thousands of bad attempts arrive. Pair the hash with edge rate limits, account-safe messaging, and an alert for unusual verification load.
Plan gradual parameter upgrades
Hashing parameters should be reviewed against current hardware and the login volume the service can absorb. Keep the encoded parameters with each hash, verify the old hash before replacing it, and upgrade on a successful login or a controlled reset. Add a bounded rehash queue only if it cannot reveal account state. Measure memory and latency under concurrency, then document the policy version so a future change is deliberate and reversible.
Implementation example
Store the encoded Argon2id parameters with each hash and a policy version in safe metadata. Verify the existing hash before replacing it, upgrade parameters on successful login or controlled reset, and never place plaintext passwords in jobs, events, support records, or analytics. Pair hashing with rate limits and safe account messaging.
argon2id(memory=65536, iterations=3, parallelism=2, salt=random_16_bytes)Verify and troubleshoot
Benchmark verification on the smallest production instance at normal and attack-like concurrency. Test malformed hashes, old policy versions, password reset, concurrent password changes, lockout limits, and a slow or exhausted worker. Monitor memory, duration, verification failures, and queue pressure without logging the password or full hash.
Operations and recovery
Review parameters as hardware and threat models change, then migrate gradually rather than rewriting every account at once. If credentials may be exposed, force reset or rehash under a documented incident plan and revoke sessions. Keep edge rate limiting and account-safe responses so a memory-hard hash cannot become an easy denial-of-service target.
References and further reading
Use RFC 9106, the OWASP Password Storage Cheat Sheet, and the Argon2 specification. Record the benchmark hardware, policy version, and upgrade trigger with the authentication design.