Enough with the Salts: Updates on Secure Password Schemes
nccgroup.trust
nccgroup.trust
There is more than one reason, but they all boil down to one fact; two accounts with the same password will hash to the same value if you do not have a salt.
As an example, if an attacker gets a dump of all the hashed passwords, the weak passwords will be immediately apparent just from an analysis of duplicates.
As you two have already discussed, it mostly provides herd safety. You can't target the 'weak ones' because all the passwords look roughly the same.
Additionally, one might say "well, just follow industry best practices", but that is based on the idea that "use bcrypt/script" is such, but it's not. The "best practice" as defined by the industry at large is mostly just to use MD5, maybe with a salt, because that's what "everybody else is doing".
The title and the start of the article suggest that salts are irrelevant. Then only a few paragraphs later, the author states that salts are indeed crucial.
Later in the article, we learn that this article isn't really about salts at all, but the importance of a high work factor / rounds.
The problem with the way this article is presented is that a casual reader will be left with the idea that salts are no longer important, which is false.
A better title would be something like, "Salts are important, but so is a high work factor"
Historically, every discussion I've seen that tried to acknowledge the "importance" of salts devolved into people defending their DIY "secret-salt" schemes. "Important salts" seem like an invitation to reject bcrypt, scrypt, and PBKDF2.
I think part of the problem is the term. I think we should stop talking about "salts", and instead just say whether a given password scheme is randomized or not.
If you want to use it, (at least with Python) you need to roll your own. You can't upload scrypt, bcrypt or PBKDF2 with your app, since code with C isn't kosher on there.
1: https://code.google.com/p/googleappengine/issues/detail?id=5...
The Python stdlib also provides a pure-python implementation if OpenSSL >=1.0 isn't available: https://hg.python.org/cpython/file/2.7/Lib/hashlib.py#l163
The whole thing can be distilled down to: "Make sure you're using a modern hashing algorithm, and pay attention to the work factor." It actually has nothing interesting to say regarding salts and or peppers that hasn't been said a million times before.
When cracking passwords, to crack the most hashes, your inner loop is iterating the salts from the database while the outer loop moves down the dictionary. Crucially, that means the salt should always be the first bits fed to your hashing function, or else the cracker can optimize away some hashing cycles across users. HMAC will do the right thing here if salt is passed as the 'key' (although the way it expands the key to the block size is somewhat flawed). Feeding the salt to the hashing function first approximately halves the crack rate versus appending salt at the end, by preventing any reuse of hash function state in the inner loop. [1]
[1] - http://article.gmane.org/gmane.comp.security.phc/2325/
Everything is so low level, manual and error prone for such a basic part of app security.
[0] https://docs.python.org/2/library/hashlib.html#hashlib.pbkdf...
[1] https://docs.python.org/3.5/library/hashlib.html#hashlib.pbk...
[2] http://pythonhosted.org/passlib/modular_crypt_format.html#mo...
[3] http://pythonhosted.org/passlib
[4] increase of rounds # or KDF change http://pythonhosted.org/passlib/lib/passlib.context-tutorial...
If it's a big monolithic library like Spring, they just deprecate APIs and add new ones (which is in fact what they do); if you're in a microframework language like Node, you're on your own to rip out your md5 module and switch it to bcrypt.
Why not?
> because suddenly all your stored passwords wouldn't work.
Passlib handles that with a concept of "deprecated algorithms"[0]: a cryptcontext has a list of algorithms it can accept as inputs to validate against, and a subset of these can be marked as deprecated[1]. The passwords hashed with deprecated algorithms will validate but they'll be flagged as needing an upgrade. The `verify_and_upgrade` method will return both the validity of the password and a new hash if it should be upgraded, so the normal pattern is:
valid, new_hash = pass_ctx.verify_and_update(password, old_hash)
if valid:
if new_hash:
# store new hash for user
# password was valid
else:
# password wasn't valid
[0] http://pythonhosted.org/passlib/lib/passlib.context-tutorial...[1] in recent versions you can even set the deprecated list to "auto", this will consider all algorithms but the default (the one used if you encrypt with the cryptcontext without requesting anything specific) to be deprecated. It's a thing of beauty.
* The value stored in the DB is actually of the form "algorithm$iterations$salt$hash".
* The current preferred algorithm and iterations are configurable in the settings.
* When logging a user in, the algorithm/iterations in the stored value are used to check the password.
* If the stored value's algorithm/iterations are out of sync with the preferred algorithm/iterations in the settings, then the last step of the login process is to re-hash the (verified correct) submitted password with the new preferred algorithm/iterations, and push that value back to the DB.
Which creates a rolling refresh of the stored values as users log in, which in turn lets you gracefully change the algorithm or number of iterations.
A classic read is https://blog.mozilla.org/warner/2014/05/23/the-new-sync-prot... (unfortunately the presentation is 404) but the blog post is enough for a read.
For just one example, why - when there's billions of humans that each have hundreds of data points which are relevant to their lives - do we only have maybe hundreds of datastores, reporting tools or visualization tools? Hint: it's not because of the universal applicability and quality of existing products.
Humans aren't very good at considering others or planning very far ahead. On top of that we're not very good yet at training people who can code despite the acute demand for them as everyone transitions paper processes to digital or old digital processes to new digital processes.
Everyone is too busy building yesterday to worry about tomorrow, even on small scales. It seems only outlier organizations have the discipline and foresight to invest in even private internal tools or processes, let alone public ones.
https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
Why not?
1) Language fragmentation. Java developers aren't going to use a C# library or a PHP library. So instead we're stuck with, "If you're on Python, use X; if you're on Ruby, use Y..."
2) Password storage is a problem that's easy to get wrong without knowing it. If you have a DIY password storage system that accepts valid passwords, rejects invalid passwords, and looks scrambled when you view them in the database, it's hard for non-experts to notice that it's broken in any other way.
3) The combination of those two problems results makes it difficult for the security community to reach consensus on good language-specific libraries. Competent Java security experts can't easily evaluate a PHP scrypt library.
So all they can say is, "allow long passwords, use per-password salts, and use PBKDF2/scrypt/bcrypt with a pretty big work factor, but don't do it yourself."
I built a machine in a milk crate that had 5xR9s to mine it. I broke even and sold it after I got bored :) It was fun for a month or two.
Memory usage of scrypt was tuned to take 128KB, which made Litecoin mining ~10 times faster on GPUs than on CPUs.
I had do decide on a hashing scheme recently and ended up going with Bcrypt just because of how new Scrypt is.
IMHO, MITM would be better defended against using HSTS, certificate pinning, and perhaps DNSSEC.
I assume the extra client-side hashing is done to keep the plaintext passwords out of the application memory, not protect it in transit.
To clarify, this is just an assumption. I have not read up on the topic nor do I claim to be a security expert. This is just what came to mind when I had the same thought as you.
Anyone else know for sure?
Meme reference aside, this is actually a sane way to do things.
https://paragonie.com/book/pecl-libsodium/read/09-recipes.md...
This is the scheme that I'm using for one of my projects. :)
// Returns binary data
shaPass = crypto.sha256(userPassword)
// returns an scrypt password
crypto.bcrypt(shaPass)
I've seen many people pass binary data into functions that will terminate reading the string at a null byte. This obviously limits the strength of the number of bytes before a null byte is hit in the binary data (mostly only concerns PHP and C).Just noticed someone else posted the ircmaxwell blog, which is the best writing on this topic.
The work factor of a password database can be changed easily, either by re-hashing every time someone with the old scheme logs in, or by hashing all the hashes more.
PBKDF2 is still widely used in applications that need to be small and simple, such as embedded devices and disk encryption tools. For hashing website passwords, though, it is showing its age.
Disclaimer: I'm not a cryptographer, so I might be talking out of my ass.
In PHP, for example, bcrypt is just `password_hash()`. To use PBKDF2, on the other hand, you have to generate your own salt and pass it to `hash_pbkdf2`.
Though IME it's bcrypt which is considered cooler, scrypt is regularly mentioned in KDF discussions but not widely available let alone used.
The big advantage of bcrypt is its cost factor is exponential rather than linear, so the changes in cost factor are much lower and less frightening (you need to double the pbkdf2 cost factor to double the cost, on bcrypt you just increase the cost factor by 1, to match Moore's Law you just need to increment the cost factor by 1 every 2 years). And that's ignoring pbkdf2 can also be tuned using the hash function.
So is PBKDF2 just fine? Yeah, that's what I generally use. But it's easier to create a simple and correct interface for bcrypt, it has less moving part and the parts which do move move slower.
- The work factor depends on the length of hash. For example, if you compute and store 64 bytes of PBKDF2-HMAC-SHA256, an attacker needs to only compute the first 32 bytes to verify the guess, and with PBKDF2 it means doing half the work.
- In most common use (with HMAC), it inherits HMAC "vulnerability": a password longer than the hash function block is equal to the its hash (see this Twitter thread: https://twitter.com/dchest/status/421595430539894784). For example, `plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd` and `eBkXQTfuBqp'cTcar&g*` have the same PBKDF2-HMAC-SHA1 hash. (Scrypt is also vulnerable, BTW, as it uses PBKDF2-HMAC-SHA256.)
- PBKDF2 is commonly used with hashes which are fast on GPU or custom hardware and require tiny memory, thus making defense/attack cost ratio very bad. See scrypt paper for cost estimates (https://www.tarsnap.com/scrypt/scrypt.pdf).
BTW, we had Password Hashing Competition (https://password-hashing.net), the (tweaked) winner of which will hopefully be widely used in the future.
* If you use the underlying hash as a black-box, each HMAC invocation compresses 4 blocks. An optimized implementation will only require 2. Many implementations (e.g. the .NET one) don't include that optimization and thus suffer from a 2x slowdown compared to the attacker's implementation.
* If you use HMAC naively, it'll hash the whole password on each iteration, opening a DoS attack. Django made this mistake (see http://arstechnica.com/security/2013/09/long-passwords-are-g... )
It abuses the underlying hash:
It xors together the outputs of different iteration numbers. This can interfere with the feed-forward of certain hash constructions. As far as I can tell this does not cause security issues, but it's still very dirty.
---
Personally I'd go so far as to say that it's worse than PBKDF1 with a sufficiently wide underlying hash.
That's a fair criticism. In that respect, a better design might have been to generate a seed S using the PBKDF2 core, then just spit out HMAC(S, 1), HMAC(S, 2) for the derived key. I don't know if that would have had its own issues though; I'm not a cryptologist.
> In most common use (with HMAC), it inherits HMAC "vulnerability": a password longer than the hash function block is equal to the its hash
True, but as you imply it's really not a vulnerability. And it's certainly better than limiting passwords to 56 bytes, as bcrypt does (with human-memorable passwords, 56 bytes really aren't enough).
> - PBKDF2 is commonly used with hashes which are fast on GPU or custom hardware and require tiny memory, thus making defense/attack cost ratio very bad.
Sure, scrypt is better. But scrypt is not what 'everyone' uses: bcrypt is.
I've never heard before that you can calculate the sitewide salt (i.e. in memory abd unknown to attacker) from having both the user password and the per-user salt. How is this done?
Or the password in case there's just a user_pw+salt :)
In that case it's simply that the "site salt" is the "password" you after it, since you know the 2 other variables you just randomly generate the 3rd one, hash it and see if it matches.
However, if your server is compromised, pulling the site-wide salt (aka 'pepper') from memory is presumably trivial, so you typically assume it is known to the attacker.
Is there a good resource that reviews attacks that actually occurred and analyzes what and how it could have been prevented?
Like this:
hash = sha1 applied recursively 4071 times ( password . salt ) . salt
Serious question. Don't just say religiously, "just use bcrypt". Tell me what is really wrong. What attacks will succeed today? Any crypto enthusiasts in the audience?
And from a purely pragmatic perspective, why even consider it? You have a bcrypt/scrypt library, writing your own code is harder and almost certainly worse, so there is literally no reason to spend even a second thinking about it.
There is a reason why Apple makes every app implement its own receipt validation scheme. So a dedicated attacker can't crack all apps at once. Same here.
The reason you want to use HMAC is the same as always, it provides better security against a number of attacks that could recover the secret, https://en.wikipedia.org/wiki/Hash-based_message_authenticat...
That being said, if you wanted to build your own _for fun_, do it! You will learn a lot in the process. Just don't expect your project to be over any time soon.
It will also (likely) have been designed and/or checked by people with domain knowledge, and so is more likely to avoid subtle attacks - just repeating a hash function n times might not offer the security you expect (e.g. 2DES is barely any stronger than a single DES pass - you need 3DES to make much of a difference).
Then there's the standardization for the crypt(3) format.