Basis Lab

Answers · Keeping it safe

How do I pick the iteration count or cost factor for password hashing?

In shortPick a time budget for one login, measure what setting meets it on your own hardware under load, and store that setting in each stored record.

Decide how long one verification is allowed to take, then measure which setting produces that time on the machine that actually serves logins, under load. Take the largest setting that fits the budget, and store it in the credential row beside the stored value rather than in your configuration.

The budget is chosen first, deliberately, because otherwise the number you end up with is merely whatever happened to be running on the day something went wrong. A quarter of a second is the common answer for an interactive login: it sits below the point where a person notices a delay, in a page that is already spending tens of milliseconds on the network. A background service with nobody waiting can afford a second or more.

1010 guesses s−14 guesses s−1=2.5×109\frac{10^{10}\ \text{guesses s}^{-1}}{4\ \text{guesses s}^{-1}} = 2.5\times 10^{9}

Put figures on both sides and the asymmetry appears. A fast one-way function takes about 0.1 microseconds, so one core produces ten million values a second and a rented card with a thousand cores reaches ten thousand million. Raise the time per computation to a quarter of a second and one core manages four a second, and the card manages four as well, because each candidate password has to be carried through the whole loop before the next one starts. The ratio between those two rates is the entire defence, and it is roughly two and a half thousand million. The attack becomes about two and a half thousand million times more expensive, and the price on your side is a quarter of a second on a login that is already doing a network round trip.

A repetition count scales linearly with the count, which is what makes it measurable rather than mysterious. Six hundred thousand repetitions at 0.24 seconds is 0.4 microseconds each; a hundred thousand repetitions at 0.04 seconds is the same 0.4 microseconds each. Double the count and you double the time, so a single careful measurement tells you the time at any setting on that machine.

0.24 s600000=4×10−7 s,0.04 s100000=4×10−7 s\frac{0.24\ \text{s}}{600000} = 4\times10^{-7}\ \text{s}, \qquad \frac{0.04\ \text{s}}{100000} = 4\times10^{-7}\ \text{s}

The cost on your side has a shape worth knowing before you set the number generously. Processor seconds required per second is the login rate multiplied by the time per verification, which is effectively a core count. At a quarter of a second each, one core serves four logins a second, so a peak of forty logins a second needs ten cores doing nothing but password verification. That is affordable, and it belongs in the capacity plan rather than being discovered during a login storm.

b=l⋅d=40 s−1×0.25 s=10 coresb = l\cdot d = 40\ \text{s}^{-1}\times 0.25\ \text{s} = 10\ \text{cores}
FIG 1One tuning session on the machine that serves logins
stepsettingmeasured timelogins per second per coreverdictwhat happened
110000 repetitions0.004 seconds250far too cheapA library default from a decade ago. 250 guesses a second per account clears a dictionary in days.
2100000 repetitions0.04 seconds25still cheapTen times better, and still six times inside the budget, so there is no reason to stop.
3600000 repetitions0.24 seconds4.2acceptedInside the quarter-second budget with a little room.
41200000 repetitions0.48 seconds2.1over budgetTwice as good against the attacker and twice the cost to you. Rejected because the budget was set first, not discovered afterwards.
5600000, under load0.31 seconds3.2accepted, recordedThe same setting with the service busy. The unloaded figure flattered it by a quarter; this is the honest number.
5 steps
The accepted setting is the fourth largest, and the figure that gets written down is the loaded one rather than the flattering unloaded one.

The number in the article is not your number

A default of ten thousand repetitions at 0.004 seconds lets one core check 250 candidates a second. Spread over a card of ten thousand cores that is 2.5 million a second, so a list of a thousand million candidates falls in 400 seconds, under seven minutes. At the measured 0.24 second setting the same card manages four a second, and the list takes 250 million seconds, about eight years. The difference between those two outcomes is a factor of sixty in work per guess, and nothing else about the system changed.

That is why a copied number is worthless: it was measured on somebody else's processor, in somebody else's year, at somebody else's login rate. Time the function at three or four settings on the hardware you will serve from, then time it again with the service busy. A 0.24 second unloaded measurement became 0.31 seconds loaded here, a quarter worse, and the loaded figure is the one to record, along with the machine and the date, because the next person to raise it needs the previous measurement to compare against.

Read the setting back from the row, never from configuration. Everything verifies until the day you raise the number, after which every account created before that day fails its password check while the password itself is perfectly correct. Because each row carries its own setting, raising the number for new rows is safe, the old rows keep verifying at their old cost, and each one can be rehashed at the new setting the next time that user logs in successfully.

What the budget does not protect against

The whole argument assumes the attacker is buying processor time, and processor time is exactly what can be bought in bulk: thousands of cores on one card, all running the same loop. Making one computation take a quarter of a second on one core does not stop ten thousand cores from running it at once.

Two failure modes come from the capacity side rather than the security side. A login storm, where an incident elsewhere sends everybody to sign in at once, turns your own verification cost into the outage; rate limiting per account and a bounded queue are cheaper answers than lowering the setting. An attacker who simply submits logins to your service makes you pay the cost repeatedly for passwords they know are wrong; that is an argument for limiting attempts per account and per address and rejecting malformed attempts before the expensive step, not for a cheaper setting. And the budget is a moving target: when the hardware gets faster, the same setting buys fewer microseconds, so the measurement has to be repeated and the number raised.

Worth keeping

  • The cost setting is chosen by measuring one verification on your own hardware at several settings and taking the largest that fits a budget you decided in advance.
  • A cost setting read from configuration rather than from each credential row breaks every existing account the first time you raise it.
  • Going from 0.004 seconds to 0.24 seconds per guess multiplies the attacker's work per account by sixty while adding a quarter of a second to a login you already spend a network round trip on.

Where this goes next

This question is one step of How a Password Is Stored, a course that works through its subject from the beginning. The page that teaches this part of it is Making It Deliberately Slow, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. Why isn't encrypting the password enough to keep it safe if the database leaks?Because encryption can be undone by your own login code, so the key that lets you check a password usually sits near the database, and anyone who copies both can read every password.
  2. Why does storing a random salt with each hash stop rainbow tables?Because the salt is different for every account, a table built once from common passwords matches at most one row, so the work has to be redone account by account.
  3. How fast can an attacker guess passwords after stealing a hash database?About ten thousand million guesses a second on one rented graphics card against a fast hash, so most of a stolen table falls in under a second.
  4. How does text I saved in a comment end up running as JavaScript in someone else's browser?The comment stayed text in storage; a page that later pasted it into its own markup made the browser read it as code.
  5. Why does escaping the quotes in user input not stop SQL injection?Escaping quotes only cleans one context; SQL injection stops only when query text and values travel separately, so the value is never parsed as code.
  6. How do I stop one service from reading another service's password?Give every service its own identity, grant only the credentials it uses, and test that it cannot read another service's password.

Read alongside