Password Storage
If your application authenticates users, chances are, it’s also going to deal with passwords.
Handling user passwords is a really big deal and handling them appropriately is an even bigger one.
It's hard to imagine a scenario worse than an application being attacked and user passwords being leaked across the internet for all to see. Personally, we shudder at the thought. So how can passwords be stored safely and according to best practices? Let’s take a look at a few ways.
Encryption versus hashing
On a surface level, one might think that encryption sounds like a decent solution for safe password storage, however, totally relying on it could be somewhat problematic.
Encryption is inherently a two-way function which, of course, means that just as a password can be encrypted, it can also be decrypted. Perfectly logical, right? Otherwise, how could you validate if a user’s password matches what’s stored in the database?
So, the ability to decrypt passwords is also a pretty big liability. If somebody compromises a server and gets a password ciphertext, there’s a good chance they'll be able to get the key material needed to decrypt those passwords, too.
Hashing, on the other hand, is far better suited for passwords due to its one-way nature. Once you've hashed something, there's no function for directly turning the ciphertext back into the original plain text. This property makes hashes uniquely suited for the protection of passwords.
Not all hashes are created equally
Once you’ve decided on hashing passwords for storage, it's also not as simple as just applying a hash function and calling it a day.
Hashes come in all shapes and sizes, most of which, are not exactly suitable for storing passwords, given all of the advances in computing technology over the last decade.
As previously mentioned, hashes can't be reversed due to being a one-way function. While that's technically true, hashing is also deterministic which means they’re also susceptible to brute-forcing tactics that can allow an attacker to potentially reverse a hash to the original plain text given enough time and resources.
For this reason, we split hashing functions into two categories:
- Cryptographic hashes
- Password hashes
A key feature of password hashes is that they have a "work factor" (either by a single integer, or multiple parameters) that defines the amount of effort it takes to calculate the hash.
As CPUs and GPUs have gotten faster over the years, it's become easier to perform massive brute-force attacks against hashes on consumer hardware, which implies that a hash can become less and less secure with time.
The “work factor” is used to ensure hashes are stored in a way that follows industry trends. As hardware gets faster, you increase the work factor of the algorithm to ensure the hash takes more time and effort to decrypt. Let’s say, as an example, 100ms on contemporary hardware.
This means that the work factor in practice may need to increase every 2-3 years.