cswork / libraryOpen practice workspace

Articles

Password Storage Interview Questions: Argon2, Salts, Work Factors, and Migration

Answer password storage interview questions on Argon2id, salts, work factors, peppers, login-time rehashing, and safe migration under concurrent resets.

An authentication service upgrades its password hashing cost. Security review approves the algorithm, but the next login surge exhausts container memory. Meanwhile, a background rehash overwrites a password reset that completed a moment earlier. Neither failure is solved by correctly spelling Argon2id.

Strong password storage interview answers connect four decisions: the offline-attack threat, the stored verifier format, the verification resource budget, and the migration protocol. Treat a password verifier as a versioned security record, not merely a hash string. To practise the surrounding architecture, start with Design a Scalable Product Authentication Platform.

This guide uses current primary security guidance and original design exercises. Example capacity numbers are illustrative, not benchmark measurements or recommended production limits. It covers application password verification, not building a cryptographic primitive or recovering users' plaintext passwords.

Why hash passwords instead of encrypting them?

An application normally needs to check whether a supplied password matches, not retrieve the original password. A dedicated password-hashing function raises the cost of offline guessing after verifier theft. Reversible encryption creates a decryption-key dependency, while a fast general-purpose digest such as SHA-256 is not an adequate standalone password-storage scheme. OWASP's Password Storage guidance recommends modern adaptive password hashing.

State the threat precisely. An attacker holding the database can try guesses without calling your login endpoint, so online rate limits do not slow that offline computation. Conversely, expensive hashing does not stop credential stuffing with a password already stolen from another site. Distinct threats need distinct controls.

Avoid saying “hashes cannot be cracked.” A weak password may still be guessed; the goal is to make guesses more expensive and reduce exposure. Also avoid storing a second reversible copy for customer support. A reset workflow should establish a new secret through an authenticated recovery path rather than reveal the old one.

What should you say about Argon2 parameters?

Use Argon2id through a maintained high-level library. Its memory cost, iteration count, and parallelism must be selected together. At the time of this review, OWASP lists an Argon2id minimum configuration of 19 MiB, two iterations, and parallelism one. That is a baseline to evaluate, not proof that every deployment using those values is adequately provisioned.

RFC 9106 gives different recommended profiles, including 2 GiB with one pass and a lower-memory 64 MiB profile with three passes, both with four lanes. Do not collapse these into a single universal “best setting” or claim that the profiles are equivalent. Explain the reference, the deployment constraint, and the measurements needed before choosing.

Keep units explicit. Some libraries accept kibibytes, others expose named policies or byte limits. In argon2-cffi's API, memory_cost is in KiB; 65536 therefore represents 64 MiB, not 64 KiB. Parallelism describes internal lanes, not the number of simultaneous users your service can handle.

For an interview recommendation, name the environment you would measure: production-like hardware, container limits, expected login bursts, and other workloads sharing the process. Record the library version and explicit policy parameters. A dependency update that changes defaults should be a reviewed configuration change, not an invisible fleet-wide experiment.

What is the difference between a salt and a pepper?

A salt is a fresh per-verifier random input stored with the verifier. It prevents identical passwords from trivially producing identical stored results and frustrates reuse of precomputed work across records. It is not a secret. Use the library's salt generation rather than usernames, timestamps, or a shared application constant.

A pepper is a separate secret used as additional protection. Keep it outside the password database, such as in an appropriately controlled secret-management system. It creates operational dependencies: verification availability, access control, rotation, and recovery. Losing a required pepper can make otherwise intact verifiers unusable.

The distinction is easiest to explain with a breach scenario. If only the verifier database is copied, a separately protected pepper can add a barrier. If the application process and its secret access are also compromised, that barrier may disappear. Therefore “we use a pepper” is incomplete without naming the assumed breach boundary.

Do not invent a concatenation recipe during an interview. Select a reviewed library/framework construction and version the pepper identifier if multiple generations are supported. Never store the pepper itself beside the salt. A rotation plan must account for which users can migrate during a successful login and which require password reset; silent deletion of an old key can lock out dormant accounts.

Build a verification capacity budget

Suppose a service chooses a hypothetical 64 MiB verification policy and allows 24 verifications to run concurrently. The working-memory budget alone is approximately 64 × 24 = 1536 MiB. That excludes the application, allocator overhead, connection handling, and other requests. It is not a prediction of total resident memory.

Now put that service in a 2 GiB container. The remaining headroom may be inadequate even though a single verification looked fast on a developer laptop. Increasing worker count can make both memory pressure and tail latency worse. Increasing internal parallelism also competes for CPU; it does not create free capacity.

MeasurementWhy it changes the decision
Verification latency at representative concurrencyCaptures contention absent from a single-call benchmark
Peak process memory and container eventsDetects swapping, allocation failure, and termination risk
Queue depth and rejected requestsShows whether bounded workers merely move overload elsewhere
Successful-login migration rateEstimates the extra cost of verifying and then rehashing
Invalid-login and recovery trafficIncludes work outside ordinary successful authentication

Use bounded verification concurrency, bounded queues, and an explicit overload response. Rate limiting should consider account and broader abuse signals without creating an easy targeted-lockout attack. OWASP's Authentication guidance also covers generic errors, safe verification functions, and automated-attack controls.

A request timeout needs careful interpretation. Returning an HTTP timeout does not necessarily cancel a native hashing operation already running. Track actual worker occupancy and release capacity when the computation really ends. Otherwise, your admission counter can say capacity is free while old work continues consuming memory.

What belongs in the stored verifier record?

Preserve the library's encoded hash format, which can carry algorithm/version information, parameters, salt, and verifier output. Libsodium's password-hashing API documents storage strings designed for verification and policy upgrades. Do not split or reconstruct that format casually.

Application metadata may additionally include a credential generation, a scheme identifier for legacy dispatch, and a pepper-key identifier when applicable. Store no plaintext password and avoid logging either submitted passwords or complete verifier strings. Operational metrics should describe policy versions, counts, and failure categories without creating a second sensitive credential dataset.

Define accepted schemes and parameter ranges at your application's boundary. A corrupted or maliciously imported encoded record should not trigger arbitrary resource consumption or fall through to a weaker verifier. Verification failure, malformed storage, and infrastructure failure are different internal events even when the user-facing response intentionally avoids account enumeration.

Consider a schema review question: can the column hold the complete encoded output for every supported scheme? Truncation may not surface until the next login. Include round-trip persistence and migration-format tests, not just a library-level hash/verify test.

How do you upgrade hashes during login?

First verify the submitted password against the stored verifier using its recorded scheme. Only after successful verification should the application consider generating a replacement under the current policy. The argon2-cffi usage guide demonstrates the verify-then-check-for-rehash pattern.

An illustrative Argon2-to-Argon2 core looks like this:

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

hasher = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)

def replacement_after_verification(encoded_hash, supplied_password):
    try:
        hasher.verify(encoded_hash, supplied_password)
    except VerifyMismatchError:
        return False, None
    replacement = None
    if hasher.check_needs_rehash(encoded_hash):
        replacement = hasher.hash(supplied_password)
    return True, replacement

This is a documented-API illustration, not a complete login handler or an executed benchmark. The selected parameters illustrate a known profile, not a recommendation for every server. Storage errors and other verification exceptions must be handled explicitly by the caller; they must never become successful authentication. Legacy bcrypt or PBKDF2 records require their own vetted verifier dispatch.

Most importantly, the helper has not safely saved anything yet. Authentication state, credential generation, transaction boundaries, and concurrent reset handling still belong to the application. Do not hand a plaintext password to a durable background queue just to make migration convenient.

Prevent rehash from overwriting a password reset

Walk through this race. Login L reads verifier H1 at credential generation 7 and verifies the old password. Reset R then stores H2 and advances the generation to 8. Login L computes a stronger replacement H3 from the old password. An unconditional update by L would overwrite the reset and restore the old credential.

Use a conditional write tied to the record that was verified:

UPDATE credentials
SET password_hash = :replacement,
    credential_version = credential_version + 1
WHERE user_id = :user_id
  AND credential_version = :observed_version
  AND password_hash = :observed_hash;

The placeholder syntax is pseudocode for bound parameters, not string interpolation. In the race above, L's condition matches zero rows after R commits. That is a concurrency outcome to handle, not permission to retry the same stale replacement without the condition.

Decide session issuance as well. A conservative policy revalidates or rejects a stale login after a concurrent credential change, rather than creating a fresh session based only on the earlier verification. Coordinate this with credential-generation checks and reset-driven session invalidation. The exact transaction design depends on the datastore and session architecture; the conditional write alone is not a complete authentication protocol.

What does a safe rollout look like?

Deploy readers capable of verifying the new format before enabling writers that produce it. Roll out stronger-policy writes gradually, monitor verification failures and memory pressure, and retain verification compatibility during rollback. Stopping new migrations is different from reverting already upgraded credentials to weaker storage.

SituationExpected behavior
Correct password, old supported policyVerify, then attempt guarded upgrade
Incorrect passwordReject; do not migrate
Unknown or malformed verifierFail closed and surface an internal diagnostic
Concurrent reset winsPreserve the reset; re-evaluate login/session decision
User never returnsApply a documented reset or retirement policy

You cannot generally transform an existing one-way verifier into the same result as hashing the original password under a new scheme. Layered legacy transitions are not equivalent to clean rehashing from a successfully supplied password. For inactive accounts, use a controlled reset process. OWASP's recovery guidance covers single-use, expiring reset tokens and consistent responses.

Practise storage decisions in a larger system

Keep protocol fundamentals in the authentication and authorization guide, then add the storage-specific follow-up below.

Practice questionPassword-storage follow-up
Design a Scalable Product Authentication PlatformBudget verification workers and versioned credentials
Fix the Password Reset WorkflowMake reset win against stale login-time migration
Design an Authenticated Online Shopping ServiceSeparate authentication, session issuance, and authorization
Design a distributed rate limiterProtect expensive verification without unlimited queues
Implement an Idempotent Versioned Database UpdatePreserve credential generations during concurrent changes

Finish an interview answer with the failure test you would run: two accounts sharing a password get independent verifiers, overload stays bounded, wrong passwords never migrate, and a concurrent reset cannot be overwritten. Those checks turn an algorithm recommendation into an operationally defensible design.

Sources and Further Reading