Verifying a submitted password against a stored value looks trivial, yet several failures cluster there — timing side channels, account enumeration, denial of service, and inconsistent handling of the submitted string. Walk through what a correct verification path must do.
answer
- constant-time equality over full length, no early exit
- unknown user → dummy derivation, identical response
- close reset + registration too, or the oracle just moves
- derivation cost = amplification; rate-limit and cap concurrency
- one encoding, one normalisation form, reject don't truncate
basics
~20 sCompare derived values in constant time; do the same amount of work for an unknown user as for a known one, using a dummy record, and return an identical response; normalise and length-bound the submitted string consistently; rate-limit because each verification is expensive; and never log or echo the credential.
solid answer
~50 sFour hazards, four obligations. **Timing on comparison.** A byte-by-byte comparison that returns early leaks how much of the derived value matched. Use a constant-time equality over the full length. The practical exploitability against a salted slow hash is low, but the discipline is cheap and the same routine protects tokens where it is genuinely exploitable. **Enumeration.** If an unknown username short-circuits, the response is measurably faster and often differently worded. Run a **dummy derivation** with realistic parameters for unknown accounts and return an identical generic response and status. Apply the same rule to reset and registration flows, or you have moved the oracle rather than closed it. **Denial of service.** Each verification deliberately costs CPU and memory, so the endpoint amplifies attacker effort. Rate-limit per account and per source, cap concurrent derivations, and length-bound input before deriving. **String handling.** Fix an encoding (UTF-8), apply one Unicode normalisation form at registration and verification alike, and impose a generous maximum length. Do not silently truncate or strip.
go deeper
Recall the basics: same generic error for wrong user and wrong password, never log the password, and use the library's verify function rather than comparing strings yourself.
Explain constant-time comparison, the dummy-derivation trick for unknown accounts, and consistent encoding and normalisation of the submitted string.
Cover all four hazards together, including the cost-amplification tradeoff created by the dummy derivation, sibling flows that reopen the enumeration oracle, and session rotation plus opportunistic rehash after success.
Position enumeration as an explicit product-risk decision applied uniformly across every credential flow, and treat verification cost as a capacity and abuse-budget question with monitoring and degradation behaviour defined.
## Constant-time comparison After deriving a value from the submitted password, the result is compared with the stored one. A naive comparison exits at the first differing byte, so its runtime depends on how many leading bytes matched. That is a **side channel**: an attacker who can measure it can, in the general case, recover a secret byte by byte, turning an exponential search into a linear one. The fix is an equality routine that examines every byte regardless — typically accumulating differences with a bitwise or and testing the accumulator once at the end — and that does not branch on the data. How serious is it here specifically? Weak, in this exact case. The attacker does not control the derived value directly; to steer it they would have to search password candidates, and each candidate costs a full slow derivation. So the salted-slow-hash comparison is the least exploitable instance of the pattern. The reason to insist anyway is that the *same* comparison discipline is load-bearing elsewhere — session tokens, reset tokens, API keys, MAC verification — where the attacker does control the submitted value and can iterate cheaply. A codebase that has one correct routine and uses it everywhere does not have to relitigate the analysis per call site. Note also that constant-time comparison must not leak through length: compare fixed-length derived values, not raw user input. ## Account enumeration The verification path is a rich oracle for "does this account exist", which feeds credential stuffing, targeted phishing and, in some products, is itself a privacy breach — membership of the service can be sensitive. Three leak channels: *Timing.* The common implementation looks up the user, finds nothing, and returns immediately, while a real user costs a full expensive derivation. The difference is enormous and trivially measurable. The remedy is to run a derivation against a **dummy stored value** with the current parameters whenever the account is unknown, so both branches cost roughly the same. This must be a real derivation, not a sleep, because a fixed sleep is distinguishable from variable real work and drifts as parameters change. *Response content.* "No such user" versus "wrong password" is the obvious leak; less obvious are differing status codes, differing redirect targets, differing validation-error shapes, or a response body that differs in length. *Sibling flows.* Registration that reports "email already taken", password reset that says "we could not find that account", and multi-factor prompts that appear only for enrolled users all leak the same fact. Closing only the login path is a common half-fix. The usual designs are: for registration, accept and send a differentiated email rather than answering in the response; for reset, always report that a message was sent if the address exists. There is a genuine tension with usability and with abuse prevention, and it is legitimate to resolve it differently for a consumer social product than for a bank. What is not legitimate is resolving it by accident. ## Denial of service through cost amplification A password function is expensive *by design*, which makes the login endpoint an amplifier: one cheap request causes hundreds of milliseconds of CPU and possibly tens of megabytes of memory. A modest flood of wrong passwords can exhaust the service, and the dummy-derivation fix for enumeration means unknown accounts cost the same, removing a natural filter. Controls: rate limiting keyed to the account and to the source, with progressive delays or lockout policies chosen against the account-lockout-as-denial-of-service tradeoff; a global cap on concurrent derivations so the process degrades by queuing rather than by exhausting memory; cheap validation before the expensive step (length bound, structural checks) so junk is rejected without paying; and a proof-of-work or challenge step for suspicious sources. ## Handling the submitted string Several quiet correctness bugs live here, and each shows up as "my password stopped working". *Encoding.* Fix one encoding, normally UTF-8, at every layer. A password containing non-ASCII characters that is encoded one way at registration and another at login will not verify. *Unicode normalisation.* The same visible character can have multiple code-point representations, and different input methods and platforms produce different ones. Apply one normalisation form consistently at both registration and verification. Do not case-fold or strip whitespace: those reduce entropy, and trimming trailing spaces means a password ending in a space cannot be typed reliably. *Length bounds.* Impose a generous maximum (large enough for passphrases and password-manager output) purely as a resource control, and reject over-length input rather than truncating it — silent truncation both surprises the user and quietly reduces strength. Enforce a minimum as policy, and prefer length limits over composition rules. *Never persist the plaintext.* No logging of request bodies containing it, no capture in error reporting or crash dumps, no storage in an audit trail, and no echo back in a response. ## After a successful verification Two more obligations belong to the same path. If the stored record's parameters are below current policy, re-derive and replace it while the plaintext is momentarily available. And treat the authentication as a session-state change: issue a fresh session identifier rather than reusing the pre-authentication one, so that a fixated session cannot be inherited.
- How serious is a non-constant-time comparison in password verification specifically, compared with token verification?Much less serious. To exploit comparison timing an attacker must steer the compared value, and here that value is the output of a salted slow derivation, so each attempt costs a full derivation and the attacker cannot aim it. For a session or reset token the attacker submits the compared bytes directly and can iterate cheaply, which makes the leak genuinely exploitable. The practice is still worth keeping uniform so one audited routine covers every call site.
- Some products deliberately reveal whether an account exists. When is that defensible?When usability and support cost outweigh the enumeration risk and the fact of membership is not itself sensitive — a general-purpose retail site, for example. It stops being defensible where membership is sensitive (health, dating, financial hardship services) or where the service is a high-value stuffing target. The requirement is that it be an explicit decision applied consistently across login, registration, reset and multi-factor prompts, with compensating rate limiting and monitoring, rather than an accident of one code path.
- Why not simply insert a fixed delay for unknown accounts instead of running a dummy derivation?A fixed sleep has different variance from real computation, so an attacker collecting many samples can separate the two distributions, and the constant drifts out of alignment whenever the work factor changes or hardware differs. It also consumes a request slot without the CPU cost, which changes the load signature. Running a genuine derivation against a dummy record with current parameters keeps both timing and resource profiles aligned automatically.
saying these in an interview costs you the question
- Returning distinct messages or status codes for unknown user versus wrong password
- Short-circuiting on unknown accounts so timing reveals which usernames exist
- Fixing enumeration on login while registration or password reset still leaks it
- Silently truncating or trimming the submitted password instead of rejecting over-length input
- Logging request bodies or error context that contain the plaintext credential
- Adding an expensive derivation with no rate limiting, turning login into an amplification target