Dustin Boswell - Brain Dumps about Computers, Programming, and Everything Else
dustwell.com About Me Past Academic Work Photo Album Links I Use
Articles by Me Subscribe: RSS
Oil ETFs leak money Yes/No proposition bets div, span, and CSS "display:" Pair programming + screen Dear VirginAmerica.com SSH keys in 2 easy steps "An hour" vs. "A hour" How to hash passwords Snapshotting with Rsync MacBook Pro Sharp Edge Fixing Your Flaky Internet How X-over-SSH works Drinking Distilled Water Politician != Decider Understanding iTunes files My Audi A4 Gas Mileage djb-dns installation Vim Cheat Sheet
How to hash passwords securely. February 9, 2010
Let's say you're making a website where users can login with a password. How do you handle passwords in a way that is secure and doesn't risk exposing the user's password to the world? Here's a simple recipe I use:
  1. 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.
  2. Store a random string (a salt) for each user in your database.
  3. 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
    ... ... ...
  4. To verify if a given password is correct, lookup the salt for that user, and compute hash(salt+password) to see if that matches.
  5. 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 email 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
... ...
(A Rainbow Table is actually a little smarter/more space efficient than this, but I won't go into the details.) This table took a long time to generate, but once it's built, the hacker community can share it forever.

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.)

Shouldn't you use https/ssl instead?

Yes, you should use it, but I believe it's not enough. You should still use a client-side hash because:

Example client-side code

Here's some example html:
<div id="fake_form">
  <input type="text" id="username" />
  <input type="password" id="raw_password" />
  <input type="button" onclick="login()" />
</div>
Note: I use a div instead of a form to prevent it from accidentally getting submitted (thereby sending the raw_password). Here's the corresponding JavaScript: (I haven't tested this - I'm just giving you an idea of how it works.)
var login = function() {
  var username = $('#username').value();
  var raw_password = $('#raw_password').value();

  // Choose which version depending on your needs
  // hash() is a secure hash function like sha-1

  // Version 1: good
  var hashed_password = hash(raw_password);

  // Version 2: better (salt the hash with your domain)
  var hashed_password = hash("example.com" + raw_password);

  // Version 3: best
  var hashed_password = hash(username + "example.com" + raw_password);

  // Now login to server by sending (username, hashed_password) ...
}
Version 3 is technically the most secure, but this only works if username will never change (probably a dangerous assumption). And if your user can login with either a username or email, then it won't work either. So unfortunately, version 3 isn't doable for most sites.
blog comments powered by Disqus