Besides slowing down attackers, the other claimed reason for using scrypt is that it is more secure against determined attackers that implement accelerated password crackers because it is a "memory-hard" key derivation function. In fact, the experience with scrypt crypto currency hashing has proven this assumption to be false. Compared with a salted hash function, scrypt like algorithms have a lower energy density when implemented in silicon and therefore actually get higher gain multiples from various levels of hardware acceleration than you observe with straight hash functions like sha256. So a determined attacker would have an even greater edge than the scrypt parameters would lead you to believe.
> KDF adds about 15 bits of work to each guess attempt.
> That's approximately equivalent to adding 6 random
> characters to your password.
A better way of putting it is that it's equivalent to adding 15 bits of entropy to your users' passwords, which in the grand scheme of isn't actually much of a marginal improvement in overall security. Adding 15 bits to an already strong password provides a small, largely irrelevant marginal gain. Adding 15 bits to a bad password has more marginal benefit, but you're typically still in the window where brute-force cracking is tolerable, especially considering typical attack models--in most scenarios attackers only need to crack the weakest password among an often large set of passwords.Fancy hashing schemes optimize for scenarios that are both relatively rare and fleeting. If you're in a position where they seem defensible, you've already lost the game. Unless your purpose is to check-off some boxes, similar to how until recently IT departments required frequent password resets to check-off NIST's antiquated guidelines.
There is a way to substantially improve the overall security of a password-based system: use a keyed HMAC on a cryptographic HSM for validating passwords. With an HSM (from which we assume the attacker can't actually recover the secret key), you can precisely, reliably, and meaningfully throttle brute-force recovery times without being contingent on any hand-wavy, snake-oily, bike-sheddy hashing scheme with dubious parameters.
When RSA 768 came within reach of brute forcing, everybody moved to RSA 2048 and RSA 4096. To make a similar leap in attacker cost for iterative hashing solutions, your login prompt would force users to sit idly for minutes, hours, or maybe even days. And making this change systemically is still underway, taking years.
Because the cost function of iterative hashing is identical between defender and attacker, you cannot pad your security in a way that is robust and generalizable against attacker capabilities in a reasonable future timeframe. How many solutions that use PBKDF, scrypt, etc, have gone back and changed their cost function (i.e. iteration count) after a year? After 3 years? After 5 years?[1] Do they tune their cost function to 8-character passwords? 10-character passwords?
And no amount of iteration within a reasonable time frame is going to prevent an attacker from cracking prototypically insecure passwords. Bitcoin becomes exponentially more difficult to mine over time. As a practical matter there's no limit to the number of bad passwords in a websites database, no matter the length.
Once you've implemented password salting, removing an attacker's ability to exponentially decrease his cost function for the next password cracking attempt, you're just putting lipstick on a pig. For people who care about good passwords, iterative hashing is irrelevant--a few extra random characters adds more cost than the authenticator would ever achieve with an increased iteration count. For all practical purposes, an authenticator's duty to attend to low-level algorithmic details ends at salting. After that, he maximizes security by redirecting all available effort to securing his infrastructure, catching bad passwords, and supplementing or replacing authentication with a passwordless mechanism.
The emphasis on specific hardware is misguided. Real-world attackers don't use FPGAs, they use public clouds and botnets. Perhaps some governments use bespoke hardware, but even then the real gains in performance still come largely from the clustering of commodity hardware. Look at supercomputers--they moved away from bespoke hardware decades ago.
Password hashing solutions that tout memory hardness and the inability to easily create bespoke hardware crackers are something dangerously close to snake oil. The features are technically legitimate but largely irrelevant in the context of real-world security. Like most snake oil, it's not that they don't add any absolute value whatsoever, but rather the magnitude of the benefit is greatly exaggerated, while the opportunity cost in misdirected resources greatly underestimated. Quibbling over a few ephemeral bits of additional attacker complexity is not a place you want to find yourself.
[1] OpenBSD was one of the first, if not the first, to both add a memory hardness aspect to their hashing algorithm and a dynamic cost function with iteration, with iterated Blowfish. But they went years, if not over a decade, before updating the default iteration count. And Blowfish is of course no longer really considered memory-hard. It's more or less an abandoned idea in OpenBSD.
> Because the cost function of iterative hashing is identical
> between defender and attacker
In the same sense as paint one can once is equivalent in financial terms to paying once cent trillion times.In the real-world, at scale the only relevant metric to his cost is the entropy of the password, which iterative hashing doesn't magically increase.
If you find it cost-effective to slap a `for (i = 0; i < 10^12; i++)` loop around your hash, you should assume it's cost-effective for your attacker as well.
These are fundamental assumption you should make in the absence of very specific exceptions not typically applicable to attack modeling of public internet services.
The use of these overwrought hashing schemes doesn't change the fundamental cost dynamic for brute-forcing password hashes. At best it's the same today as it was 30 years ago. At worst, because of botnets, he can scale must more cheaply than you.
The arguments in favor of these schemes are pragmatic, not scientific. But they're predicated on the defender constantly keeping up to date with hardware advancements as that margin is thin and evolving rapidly. Worse, it completely ignores the fact that if I've recovered the password digest for one account, I've likely recovered the digests for dozens, hundreds, or perhaps thousands of accounts with equivalent authorizations. Because of exponential cost differences between good passwords and bad passwords, and the prevalence of bad passwords, I can profitably filter out the good passwords by setting a fixed absolute per-password cost ceiling, substantially lowering my asymptotic costs.
Iterative and memory-hard password schemes are simply fighting an uphill battle. Once you move beyond salting you're just throwing good money after bad money by attempting to secure bad passwords.
Good passwords are already secure; bad passwords can't be fixed by the hashing scheme du jour.
> If you find it cost-effective to slap a `for (i = 0; i < 10^12; i++)` loop around your hash, you should assume it's cost-effective for your attacker as well.
If your work factor is higher, it takes much longer to brute force a password. On my laptop, bcrypt with work factors 13 and 14 take ~650ms and ~1100ms, respectively. In comparison, SHA256 takes 0.110ms.
A database of passwords hashed with SHA256 will take days, maybe weeks to recover most of the passwords through brute force. With bcrypt, it would take months or years.
This article[1] has some numbers (although they're a bit outdated now):
> How much slower is bcrypt than, say, MD5? Depends on the work factor. Using a work factor of 12, bcrypt hashes the password yaaa in about 0.3 seconds on my laptop. MD5, on the other hand, takes less than a microsecond.
> So we’re talking about 5 or so orders of magnitude. Instead of cracking a password every 40 seconds, I’d be cracking them every 12 years or so. Your passwords might not need that kind of security and you might need a faster comparison algorithm, but bcrypt allows you to choose your balance of speed and security. Use it.
Or it will take seconds on special purposed hardware, not much longer than salted sha256. In the mean time you'll have made poor security decisions based on the invalid assumption that your attacker will need months or years of compute...
EDIT: Besides, bcrypt is not even the "best" hash function we have available. If you're concerned about GPUs and FPGAs, there's argon2id which has much stronger guarantees than bcrypt.
Not too good advice if you ask me IMHO.
Here's better advice: https://codahale.com/how-to-safely-store-a-password/
s/bcrypt/scrypt/g if you prefer.
"Replacing libsodium. Libhydrogen focuses on being small and is for environments where libsodium cannot be used."
Since this is relevant to the original topic, the password hashing API is a good illustration of the difference between both libraries. New additions to libsodium must remain consistent with historic APIs, that have limitations and usability issues. Hydrogen learned from this, and tries to model its API after common problems to solve in real-world applications rather than provide interfaces to cryptographic primitives. This will eventually become the base of libsodium 2.x
Of course, there's nothing wrong with using libsodium, if only because it's way more mature and implements conservative designs that you can trust.
But feedback on Hydrogen would also be really great. I really want to avoid doing the same mistakes again, and build the best possible easy-to-use hard-to-misuse APIs that everybody can use confidently.
I imagine the later author wanted things this way.