- Always one-way-hash (with a salt) the user's password on the client (using Javascript most likely). So when your user types "my_password" into the password field and hits "Log In", the browser will send something like "0x22cd3f2e3f2e56f7ecf5..." instead. There's never any need to "decrypt" this. The rest of the system behaves exactly the same as if "0x22cd3f2e3f2e56f7ecf5..." was their actual password.
- Store a random string (a salt) for each user in your database.
-
Instead of storing the user's password, store hash(salt+password).
Here's an example of what this looks like:
username salt hash(salt+password) BillyBob 0xd029d0f092c09a09b 0xa0947cf7abd520 BettySue 0x9017d09082ceaa0fc 0x0014bcfd8be781 ... ... ... - To verify if a given password is correct, lookup the salt for that user, and compute hash(salt+password) to see if that matches.
- If a user forgets their password, send them an email with a link to a one-time url where they can enter a new password. Compute hash(salt+new_password) and store that in the database.
Why storing plain text passwords is bad
Let's say that you just store all the passwords in plain text. The problem is that some nefarious hacker might gain access to your database:| username | password (plain text!) | |
|---|---|---|
| BillyBob | billybob23@gmail.com | Password123 |
| BettySue | bettysue@yahoo.com | iLoveChocolate |
| ... | ... | ... |
The bigger problem is that most people re-use passwords across multiple sites. So a smart hacker could try to login to billybob23@gmail.com's email account using the password "Password123". Think about all the information someone can get out of an email account (bank records for instance).
If that password doesn't work for BillyBob's email, the hacker could then try using that username/email/password for a number of popular banks, like bofa.com and chase.com
And if that doesn't work, the hacker will move on to BettySue's information. The hacker will probably be successful for a good fraction of your users. So do your users's a favor, and don't store passwords in plain text.
Why you need salts in your database
Why isn't it good enough to store hash(password) in the database? Hashes are one-way, so it's impossible to "undo" a hash, right?
Well, yes and no. The problem is that hackers have computed Rainbow Tables for many well-known hashes (like md5, sha1, sha256, etc... -- all the hashes that you might be using). In a nutshell, a Rainbow Table is a giant list (like hundreds of millions) of common passwords along with their hash:
| password | hash(password) |
|---|---|
| a | 0x2d08d232d9823d |
| b | 0xfe8f8c8a8e8d82 |
| ... | ... |
| Password123 | 0xc0c27d8f9dee475c |
| ... | ... |
| iLoveChocolate | 0x32c243e37333489e |
| ... | ... |
Now it's easy to "reverse" the simple hash(password) in your database -- they just take the hash like 0x32c243e37333489e, look it up in the Rainbow Table, and viola! the password is iLoveChocolate.
This is why you need to add a salt to the input of your hash. So instead of storing hash("iLoveChocolate"), you would be storing hash("this is your random long salt." + "iLoveChocolate"). This makes the Rainbow Table ineffective because "this is your random long salt.iLovChocolate" is unlikely to be in the table. The actual salt you use should be a good long (and random) string, perhaps 100 characters or greater.
Why you need per-user salts
You might be tempted to just use a single salt value for all users, and not have to deal with storing each user's salt in the database. But this is less secure.For one, the hacker could start to build his own Rainbow Table, where "this is your single fixed salt value" is prepended to each password first. It would take a while, since the hacker would have to compute hundreds of millions of hashes. But eventually he might be able to match one of the hashes in your database to one of the entries in his newly-built Rainbow Table.
Also, by having a single salt for all users, the hacker would be able to know if 2 users happen to use the same password. If he sees 2 users with the same hash(fixed_salt+password), it must be because they have the same password. Thankfully, he doesn't know what that password is (yet). But a password that is used by two users is likely to be very weak, and so he could focus his attack on those 2 users.
So it's a good idea to have a per-user salt. This makes the above two attacks much less likely.
Why you should hash the password on the client also.
When the user types their password into your <input type=password> and submits that form, that password value is sent across the internet in plain text, and given to your web server, which might even log all the parameters for all the requests it gets.That's a lot of opportunity for the password "iLoveChocolate" to get stolen. That's why it's better to hash that password in the browser first, so their password never leaves their computer. Note that a hacker could still sniff the hashed password going over the network, and use that hash later to send to the server and impersonate you. But at least the hacker can't use your real password for other purposes.
For the same reasons as above, it's better to salt this hash too. (In general, it's always more secure to add a salt before hashing. The saltier, the better.)
You might be thinking "Shouldn't you use https/ssl to be more secure?". SSL is often slow, difficult to setup, or simply unavailable for your server environment. It also doesn't prevent the problem of the web server accidentally logging the user's (plaintext) password to a file.
But encrypting the communication between browser & server does help prevent sniffing the request params and impersonating that user later, so it's better than not using it. Just don't forget to hash the plaintext password on the client-side anyway.
blog comments powered by Disqus