On Users and Passwords

Update: thanks to the commenters for pointing out some flaws in the logic of the previous version of this page. I’ve updated the page to incorporate their feedback.

I’ve been thinking about adding wish lists to Booko. Wishlists require an implementation of Users – after all, what’s the point of having Wishlists if you can’t change them or publish them?  Booko’s built on Ruby on Rails, so I had a look around for plugins, but, truth be told, I’m too much of a Ruby n00b to trust other people’s plugins.  I’m sure they’re easy to install, but how do you keep them up to date? Finding any kind of bug will mean reading and understanding the code and seriously, that’s as much work as implementing Users on my own.  Plus, I have concerns about their implementation which I’ll discuss more later.

So, I figure having users requires two bits of data:

  • Email address
  • Password

Having the email address as the login name makes sense to me – it’s unique and if someone forgets their password I can email them a password reset link.  No need to remember a separate username.  One day I’ll add OpenID because I’m a freetard and like the concept.

Now, passwords are valuable bits of information and they need to be protected. This may sound obvious, but they need to be protected for a couple of reasons:

  • only the actual user (or owner of the email address) can log in and manipulate Booko Wishlists
  • many users use the same email address and password on other sites (PayPal & Amazon for example).

Maybe you don’t do this, but if you have a separate password for every site requiring login, you’re a better person than me. The consequence of someone getting hold of your email and password can lead to some … difficulties on sites you’ve used the same email address and password.

So, we need to protect passwords from prying eyes.  There are two main ways for your password to be discovered:

  • Database compromise
  • Web sniffing proxies.

So, what to do? What we do is turn to hashing functions. Hashing functions (like MD5 & SHA*) can take information like a password and send it on a one-way trip.  In effect, it is impossible to work backwards from the hash to the password, however, some clever people have calculated the hash of hundreds or millions of passwords and stored the hash – then they can simply lookup the hash stored in Booko with their pre-calculated hashes and find the password. This is known as a dictionary attack.

To thwart this attack, we introduce a “salt”. The salt is a random string of characters which is combined with the password before it is hashed.  This means that the dictionary attack is now useless – they would need an entire dictionary which included your salt combined with all those guessed passwords. The correct way of combining a password with a salt is to use a HMAC function.  In Ruby you can do this like this:

[sourcecode language=”ruby”]
require ‘hmac-sha1’

def self.do_hashing(password, salt)
passwd_hmac = HMAC::SHA1.new(salt)
passwd_hmac << (password)
passwd_hmac.hexdigest
end
[/sourcecode]

Ideally, each User has their own salt. This means an attacker would need to generate an entire dictionary attack per user.

In any case, this is where the other implementation of Users seem to stop. When you type your password into a form on a web page, it gets sent to the server – the server hashes your password (along with the salt) and checks if that hash matches the stored hash. If they match, you’re in.

But there is still the matter of your password being sent over the internets possibly via proxy servers which can listen in on the traffic. There’s two ways to stop the password being sent in the clear. Either hash the password first, or use SSL. If you’re using SSL to send the passwords, you could stop here. Booko currenly doesn’t have SSL certificates so we need to stop passwords travelling over the internet in the clear. How do we do that? Easy, we hash the password before sending it over the internet. We’ll create a single salt for this hash and call it the transport salt.

On the server side, we’ll also hash the password with the transport salt, prior to hashing it with the per-user salt.

This means we send the browser the transport salt, and the browser calculates the hash. This hash is sent to the server. The server can validate the password as shown below:

[sourcecode language=”ruby”]
require ‘hmac-sha1’

def self.do_hashing(password, salt)
passwd_hmac = HMAC::SHA1.new(salt)
passwd_hmac << (password)
passwd_hmac.hexdigest
end

final_hash_to_check = do_hashing(password_hash_from_browser, user_salt)

if final_hash_to_check == stored_password_hash
# User provided correct password.
session[:user_id] = user.id
end

[/sourcecode]

In the code above, when the response comes back from the client, the server calculates the final hash by using the user_salt. This final hash is compared with the stored_password_hash – if they’re the same the client provided the correct password.

So, where does this leave us?

  • At no point is the password sent in the clear, nor stored in the clear.
  • The client only sees the transport salt, not the per-user salt and can’t precalculate a dictionary attack
  • Each user has a separate salt, making it far more difficult for an attacker to perform a dictionary attack
  • Compromising the database and retrieving the hashes doesn’t allow you log in with that hash
  • Compromising the database and retrieving the hashes doesn’t allow you to log onto any other sites
  • Sniffing the hashed transport hash will allow an attacker to access your account

So, we’ve achieved some of our goals. The password is never sent in the clear. However, if an attacker snoops traffic between the client and server and get’s a copy of the hashed password, that password can be used to log on to the service.

If that’s unacceptable, then moving to SSL is the next step. You can use SSL to protect the hash as it moves between the client and server. However you can also use SSL to protect the plain password being discovered too. Is there any point in doing both? Hashing the password prior to SSL is slightly more secure. If you hash the password before it leaves the client, there’s no danger of the password appearing in the log files of your web sever or application server. If you have a dedicated host decrypting the SSL prior to passing it to your web server, the password could be sniffed between those servers.

After all that, I’ve decided that Booko will use both hashing the password before transmission and, eventually, SSL.

Notes on SHA1 and extra security:
SHA1 hashes are very secure and can be calculated very fast. The faster the hash, the faster you can create a dictionary attack.  Ideally for this scenario, we want a slow hashing function, or, more correctly, we want the method that generates our hashes to be slow. This can easily be achieved by simply hashing the password multiple times.  Here’s some timings of hashing:

[sourcecode light=”true” language=”ruby”]
>> require ‘hmac-sha1’
>> require ‘benchmark’
>> salt = (0..256).map { ((0..9).to_a + (‘a’..’z’).to_a + (‘A’..’Z’).to_a).rand }.join
>> hash = nil
>> Benchmark.realtime { hash = HMAC::SHA1.new(salt)<< "MyPassword" }
=> 6.60419464111328e-05
>> Benchmark.realtime { 100.times { hash = HMAC::SHA1.new(salt) << hash.hexdigest } }
=> 0.00437498092651367
>> Benchmark.realtime { 1000.times { hash = HMAC::SHA1.new(salt) << hash.hexdigest } }
=> 0.0426349639892578
>> Benchmark.realtime { 10000.times { hash = HMAC::SHA1.new(salt) << hash.hexdigest } }
=> 0.463771820068359
>> Benchmark.realtime { 100000.times { hash = HMAC::SHA1.new(salt) << hash.hexdigest } }
=> 4.64294099807739
[/sourcecode]

So, 10,000 hashes took around 1/2 second on my 2.8Ghz Core 2 Duo MacBook Pro. You can see that performing 10,000 hashes will seriously slow any attempt at creating a dictionary attack on your passwords. I haven’t timed how long it takes in Javascript, but you’d want to keep it under a second to make user log in not too painful.

Links:

You can find javascript libraries for doing HMAC on PAJ’s Homepage or jsSSH on sourceforge.

6 thoughts on “On Users and Passwords

  1. Did you consider OpenID? I think there are rails plugins that will just do it for you.

    Also, I may have not quite understood the algorithm but if I’m a malicious user I can get your salts and the hashing algorithm you use. Doesn’t that mean that I can still do a brute force attack on Booko as long as I keep the same browser session open?

    Lastly, isn’t the salt supposed to be secret and different for each user? e.g. the id of the user object in your database or the date of joining or some other such semi-random and secret information.

    1. Yeah – I will add OpenID one day. Not sure if I’ll use a plugin though.

      And yes, any user with an account can get hold of the salt. The salt by itself is not terrible useful, but it can be used to generate a dictionary for a dictionary attack (where you calculate hash for millions of passwords), but the database still needs to be compromised for the attacker to get the hashed passwords (to look up for the dictionary attack). If you’re using per-user salts, then an attacker who has your password hashes also has your per-person salts.

      Now, in effect, any attacker who has your hashed passwords will also have your salt, whether it’s a single salt for all users or per person salts. Naturally they’ll have had longer to generate the dictionary attack for your single salt, but they can still generate dictionaries for you single-user salts too. You can make your hashes more expensive to calculate by doing lots of them (EG 10k hashes takes around 1/2 second – slows down the generation of that dictionary.).

      The trade off, is the above issue Vs sending passwords in the clear during login. The first is a theoretical risk, the second is a real risk. Naturally I’m a n00b at this stuff – I’m all ears if I’m missing something.

  2. One of the purposes of password hashing is to prevent a compromise of the password database giving an attacker all the user credentials to the application.

    In your model, the password hash effectively becomes the password because the client performs the hashing before submitting the password. An attacker with access to the hashes can just skip that piece of javascript. The only benefit of storing the passwords in a hashed format is to prevent collateral damage caused by people reusing passwords.

    The standard mechanism of using SSL to encrypt the authentication credentials, then hashing server side and comparing hashes, is much better. SSL certs are cheap (under US$50/year from some providers).

    BTW – There are significant benefits to using per-user salts. In the event of database compromise, a static salt gives no additional benefit, as the attacker only needs to compute 1 set of hashes to get any password. With a per-user salt, an attacker needs to be computing a completely different set of hashes for each password. I can’t really see the downside, I can’t imagine it requires much code to implement.

    1. Andy – you’re correct. Although the password hash would still need to be hashed before being sent to the server, having a copy of the hash is enough to allow you login. The downside I saw with per-user salts was that the client would need them to perform the hash – which would mean maybe an ajax call to retrieve it. But that’s incorrect.

      I’ve (substantially) updated the post. Thanks very much for your comments.

    1. Hi Pete,
      Mostly Textmate with Firefox + Firebug. Although Safari’s developer tools are pretty good now too. I run the local dev version of Booko via mongrel (but I’ve been using Thin more and more). Sometime VIM. And GIT for scm.

      Cheers,
      Dan

Comments are closed.