This post comes to you in three parts: An introduction to the most underutilized feature of argon2, introducing a python project to utilize that feature, and a rant about the current terminology on password storage.
argon-wat?
argon2 is the algorithm that in 2015 came out as the winner of the Password Hashing Competition. The goal of PHC was to find an algorithm that was thoroughly analyzed by experts so that we could standardize on for storing passwords, since there was an abundance of different schemes at the time.
The key tenet of modern password storage is assuming that your database will leak at some point, and that the storage scheme should ensure it’s as expensive as possible for an attacker to reverse your users’ passwords. While we as defenders run our chosen scheme on servers, an attacker can pick a setup optimized just for crunching passwords, ie. a custom circuit built for the purpose (ASIC), FPGAs or a multi-GPU beast. “As expensive as possible” in this world means trying to ensure our scheme runs optimally on our setup, not on the attacker’s.
And on our setup, we don’t need an answer immediately. While an attacker probably has millions of hashes to try to reverse, we’re only processing one at a time. Granted, we have a user on the other end that’s waiting for us so that they can get on with their life, but we can afford to let them wait for a couple of milliseconds. Bumping the processing time from microseconds to milliseconds doesn’t really impact our user in any way, but we literally just ensured the attacker needs to bump their budget 1000x to reverse the same amount of passwords they did before our speed bump.
Something else that’s available to us on our servers is memory. Logins are fairly infrequent compared to return visits, thus we’re not overly concerned about making the process require, say, 512kB of memory instead of just a hundred bytes or so. In that swift stroke we just bumped our attacker’s budget with another 5000x.
Servers also have multi-core CPUs available to them, thus we can also ensure the process uses, say, 20 threads, and that’s another 20x budget bump for our attacker, who’s at the moment crying after having invested in a $1000 password crunching setup before realizing they need another hundred billion dollars to fund a $104,840,000,000 setup to reverse our new hashes.
While we can pat our backs and be quite confident attacker’s won’t be able to reverse all of our hashes, an attacker might not be interested in all of our hashes. In a leaked database every hash will probably be associated with an email address, and to most attackers a .gov, or .mil email is probably significantly more interesting than most others. For many setups, the attacker only needs one password to fully compromise an organization, as that password might open up an email account that grants the rest of accesses needed to compromise an organization.
Also aiding our attackers is our fallibility as human beings to remember 20+ character pseudo-random strings for every website we use. Password managers is the bane of every hash-reversing attacker, but they are far from ubiquitous yet. No matter how expensive we make it, it’s never going to be hard for an attacker to reverse “password123” and “” passwords. Oh, you don’t think an attacker knows your address? Why, they have your email address, odds are it’s possible to figure out who you are and where you live from that. Thus assume that if you’re an interesting target, they’ll be guessing your password with every combination of words from a dictionary built on words from your language, your address, your family member’s names, your pets, the place you work, your interests, and so on.
But we can end this. All of our approaches so far to making it expensive have been about leveraging the differences in our setup to our attackers. An in 2017, the vast majority of passwords we have are for a web service. For most setups that means some application code that manages access to a database. Which means that we have a logical layer in front of the storage layer. By utilizing this we can ensure that even if our storage layer is compromised (potentially through a bug in our logic layer), even a single password hash is irreversible.
argon2 enables us to do this, by folding in an arbitrary secret into the process, ensuring that an attacker without that secret cannot reverse the hash. Practically you can think of it as instead of reversing one password, an attacker now has to reverse two: both our user’s password and one we choose. And we don’t have to remember ours, thus we can generate a 30 character random string, and as long as we keep this value out of the database, our attacker is stopped dead in their tracks.
That is of course assuming that an attacker only has compromised your database. If you’re entire infrastructure is compromised, the attacker will be back to the $100B setup and can reverse hashes, albeit slowly. We’re no worse off than we were before introducing the secret. But if only the database is compromised, like through sadly-still-the-most-common-vulnerability-on-the-web SQL injections or a leaked database backup, not a single password will be reversed from your hashes.
We can also ensure that even if our secret is leaked, it doesn’t impact all of our hashes. In a naive setup, rotating the secret would invalidate all of your passwords, forcing your users through the password reset mechanism to set a new one. argon2 however enables storing a secret identifier in the hash, so that we can know for each password which secret was used. Thus if we rotate our secret regularly and the secret is compromised in July, and our database is compromised in September, only users who didn’t log in to the site at least once in the period between the two events can have their hashes reversed.
This is possible since whenever a user logs in, we can re-store their hash if the secret currently used for them is not the latest. We have to keep all old secrets around to enable verifying passwords with old secrets, but whenever we store a new one we’ll use the latest we have.
Introducing: The solution to all of your problems
*Sniffs* Do you smell cinnamon?
The only sad part about it all is that the secret argument you need to pass to argon2 is not exposed through the high-level APIs in the reference implementation, which has led to the most common argon2 libraries in most languages not enabling this functionality at all. This is where kittens and puppies cry. However, with an aptly named package I’ve recently published to PyPI this is now available to you if you use python:
from porridge import Porridge porridge = Porridge('myfirstkey:myfirstsecret') def create_user_password(user, password): user.boiled_password = porridge.boil(password) def validate_user_password(user, password): valid = porridge.verify(password, user.boiled_password) if valid and porridge.needs_update(user.boiled_password): create_user_password(user, password) return valid
Yeah yeah, I heard your “Woooo… dafuq?!?” Skip to part three if you want to read about my motivation for the naming, but in the meantime, assuming you have a fairly normal setup, the code above does everything we talked about above. It wraps argon2 with strong parameters for memory usage, time and parallelism, and uses ‘myfirstsecret’ as the secret. When it’s time to rotate the secret, initialize Porridge with mysecondkey:mysecondsecret,myfirstkey:myfirstsecret instead. This also re-stores the hash on logins if either the secret has been rotated or the parameters for memory, time or parallelism has changed. Currently available in your favorite cheese shop.
If you’re not on python, tough luck. I’m hoping someone will make similar packages available on other platforms as well, you only need a class with three methods, so it’s not a big effort, but you do need to build and interface with native code, which is most of the hassle. In my project, there’s more setup and build code (~1000 lines) than actual python code (~700 lines), but that includes building wheels for all major platforms and all recent versions of Python to ensure it installs cleanly on Linux, macOS and Windows, even without a C compiler. Bon appétit.
Now to part three, in which I’ll be explaining the reasoning behind the naming seen in the module. Opinions ahead.
There are only two hard problems in computer science…
…naming things, cache invalidation and off-by-one errors. This is about the first one.
I’ve always hated hearing the word “hash” when we’re talking about passwords. A hash is a very well-known concept used often when writing software, but when we’re storing passwords, hashing them is the last thing you want to be doing. The process does indeed utilize a hash function somewhere, and it does have the same one-way properties as hashing does, but all the other functional requirements are stark opposites. Hash functions should be as fast as possible and require as little memory as possible, password storage should use as much of both as feasible in a given environment. Passwords should also be salted, use server-side secrets, and a lot of other stuff you’d never think of yourself if you had no information security background and saw the two words “password hashing”.
There is the concept of “key stretching”, which largely only covers the time aspect, and has to be combined with salting and memory-hardness to be worthy of anything. Thus since I haven’t been able to find any better terminology for password storage, I made something up.
“Boiling” was what I came up with, which is a close enough analogy to be useful. It’s a slow process, requires several ingredients (often salt), and is usually a one-way process, once the ingredients have been boiled, they’re hard to separate again. That is decently close to slow, salted hashing for a well-known concept. Naming conflicts should also be few, there’s no known project that combines “boiling” and “passwords”, so it should be hard to confuse with something else, and easy to find with search engines.
This, combined with my agreeing with Eran Hammer on module naming, led to the porridge module, in which I boil passwords. If anyone comes across a table “boiled_password” in my database or overhears anyone talking about “boiling passwords” at a conference, googling the term makes it impossible to confuse with plain hashing of passwords.
One final note, remember that security is never a binary thing, and authentication on the web is complex topic. Just because you do everything that this post recommends for storage, it’s only a small component in the field of passwords. You’re still going to need rate limiting, password policies, U2F/2FA, secure resets, etc. Hire someone who knows how this works to design your system.
tl;dr
Calling password storage “hashing” is dumb because it’s too easy to confuse with plain cryptographic hashing, call it “password boiling” instead; use porridge to boil passwords to also include a server-side-only secret to prevent reversal if your database is leaked.
Thanks to Nicolay Broby Petersen and Kyle Lady for reading drafts of this post and providing valuable feedback.