Most of the security problems with passwords occur after an attacker has gained access to your server, and is able to view the database where you store passwords. While it’s definitely a good idea to lock down your server to prevent unauthorized access in the first place, you should still implement damage control for the worst case scenario.
Use OAuth Instead, If You Can
The best way to deal with passwords is not at all. Unless you have a specific need to handle passwords directly, you can use OAuth to have someone else handle it for you. This is also called third-party sign-on, and you’ve probably encountered it before if you’ve ever been asked to sign in with Google or Facebook.
OAuth is more complicated than password authentication, but even if you’re compromised completely, there is zero password data for an attacker to see, not even hashes.
Never Store Plaintext Passwords
If you have to store passwords, you should never store them in plaintext on your server. “Plaintext” means it’s readable by an attacker with access to your disk. For example, if you simply take a user’s password and store it in your MySQL database, that’s storing in plaintext. This is why you’re always given a link to reset your password instead of the company just telling you what your old password was.
The solution to the plaintext problem is hashing. A hash is a function that takes a value and generates a unique key. For example, the phrase “password” has a SHA256 hash of:
But changing even a single digit (passwerd) changes the output completely:
So, rather than storing the password on the server, you store this hash. Hashes are different from encryption in that they’re one-way functions. You can hash something, but it’s impossible to “unhash” it without just straight up bruteforcing the hash. This means there’s no secret key to store, and even if an attacker gets their hands on a hash, they’ll have to bruteforce it first to see the contents.
This plaintext rule applies to auxiliary things like log files as well—if the attacker can read it from anywhere, that’s a major issue. This also applies to plaintext transmission methods like HTTP, though you should never send passwords over the wire anyway. You want to generate a hash on client side when they enter it in, to prevent passwords from being sniffed over the network.
Even though securing traffic with HTTPS would prevent man-in-the-middle attacks on the client’s side, if an attacker had access to your server, they could decrypt and sniff newly created passwords. This also makes your service more trustworthy, as a user won’t know if you’re secretly storing their password behind the scenes. But if you only see a hash, even the server doesn’t know what their password is.
If you simply want a good hash to use, choose PBKDF2, as it’s used specifically for storing passwords and is very secure. You’ll probably want to use the JavaScript implementation on the client side, but if you must use it server-side, you’ll want to use an implementation for your language.
Salt Your Passwords
Hashing has a problem, and regular password hashes can be cracked with a method known as rainbow tables.
To attack a hash, you could simply try every single possible password for each hash entry in your database, which is known as bruteforcing—slow, but not entirely impossible, depending on how weak the password is and the hash used to store it. It may take a few days or weeks of computation time, but an individual weak password could be cracked eventually.
Rainbow tables speed this up dramatically. Rather than bruteforce each password individually, the hashes for every possible password are computed beforehand and stored in a file. This file can be massive, on the scale of many hundreds of terabytes. All it is is a key-value pair of each possible password (up to a certain size, depending on the table), and the corresponding hash.
It’s a tradeoff of storage space for time; you only have to perform the hash once, then you can look it up in the table instead (which is much quicker). These tables are publicly available, and easy to generate.
To prevent this attack vector, you should add a salt—a random string that you append to the end of the password before hashing. Instead of hashing “password“, you would hash:
This salt is stored alongside the password hash in the database. When a user enters their password, you send the salt back to the user so they can add it to the hash. You can think of it like each user having their own unique rainbow table, which defeats the purpose of them entirely.
The salt itself isn’t secret. It doesn’t have to be, as all it is doing is preventing rainbow table creation, and you’re storing it in plaintext anyway. Salted passwords can still be bruteforced individually.
In practice, hash sizes of 32 bytes are fairly common, as really short hashes are still vulnerable to rainbow tables. And don’t reuse salts; you should generate a new random string each time.
Use a Secure Hash Meant for Passwords
While SHA256 is a secure hash, it’s also designed to be a general-purpose hash. This means it has to be fast, because it’s also used for creating checksums (which must process gigabytes of data). Speed directly decreases bruteforcing time, and even with salted passwords, it’s still relatively easy to crack individual short strings. Salts only protect against rainbow tables.
Instead, use PBKDF2. It’s meant specifically for passwords, meaning it’s relatively slow to calculate for the average length password. It takes much longer to bruteforce, and it’s practically impossible to crack longer passwords stored with it. You can use the JavaScript implementation, or use a server side implementation.
To make full use of PBKDF2, you’ll want to implement some sort of password standard for your site. You don’t need to require everyone to have dollar signs and numbers in there; length matters much more than anything else. Try to enforce 8-12 character passwords at a minimum.
A Final Checklist
In closing, here’s a security checklist to make sure you’re all set:
Avoid using passwords and switch over to OAuth if possible. Never store plaintext passwords in any database, log, or file, and never transmit them over HTTP connections. Hash passwords with a secure hash function like PBKDF2 or SHA256. Always add a random salt to your password hashes, and store it alongside the hash. Avoid using MD5 or SHA1. (They’ve both been broken, and are insecure. ) Enforce decent password standards for your site’s users. (Length is key here. ) Ideally, keep your server entirely unaware of plaintext passwords in the first place by performing the hash on the client’s side. This future-proofs password protection even in the event an attacker gains full memory access to your server. Make sure the server itself is secure by locking down SSH access and keeping everything up to date, so you will likely never have this problem in the first place.