Users and Passwords

Posted by Koz Monday, August 03, 2009 03:53:00 GMT

Last week I lost several productive hours resetting my ‘insecure’ password on several websites due to a security breach at a website I haven’t used in more than a decade, if you’ve ever used that site, you’d be well advised to change your password pretty much everywhere. In order to prevent this happening in the future, I figured I’d write up a simple best practices article on handling passwords and authentication. There’s nothing particularly new here, but it’s always worthwhile revisiting the basics.

What Not to do?

Never store your users’ cleartext passwords in your database, no exceptions. The most important reasons is that if your site is compromised or your backup drive lost all your users will be in danger. The attacker will have access to a ready-made list of passwords and the related email addresses which they’ll be able to go off and cause havoc with other websites. The second risk is that any one of your staff members could steal that information and use it to attempt to access other systems where your users have used the same password.

While it’d be nice if users never reused a password, the reality is that nearly everyone does it. We need to be responsible and realistic, and take the necessary precautions to protect our users.

Instead what you should be storing is a hash of the user’s passwords. This will let you verify that the password provided matches the one on file, but will never let you recover that password.

Salting

But just storing a hash of the passwords isn’t enough, this would still leave you open to rainbow attacks where an attacker pre-calculates hashes of millions of passwords, then compares the hashes with the values they’ve stolen from your database. To prevent this you need to salt them. This means storing a small random value against each of your users and adding that to the password before you hash it.

Putting it Together.

So this leaves us with a user model with two relevant columns, salt and hashed_password, leaving us with a simple migration like:

1
2
3
4
5
  create_table :users do |t|
    t.string :email
    t.string :hashed_password
    t.string :salt
  end

We’ll also need two kinds of method on the model itself, hashing methods and verification methods. We’ll cover the hashing methods first:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  require 'digest/sha2'

  class User < ActiveRecord::Base
    # Create two virtual (in memory only) attributes to hold the password and its confirmation.
    attr_accessor :new_password, :new_password_confirmation
    # We need to validate that the user has typed the same password twice
    # but we only want to do the validation if they've opted to change their password.
    validates_confirmation_of :new_password, :if=>:password_changed?
    
    before_save :hash_new_password, :if=>:password_changed?
    
    # By default the form_helpers will set new_password to "",
    # we don't want to go saving this as a password
    def password_changed?
      !@new_password.blank?
    end
    
    private
    # This is where the real work is done
    def hash_new_password
      # First reset the salt to a new random string.  You could choose a
      # longer string here but for a salt, 8 bytes of randomness is probably
      # fine.  Note this uses SecureRandom which will use your platform's secure
      # random number generator.
      self.salt = ActiveSupport::SecureRandom.base64(8)
      # Now calculate the hash of the password, with the salt prepended, store
      # store that in the database
      self.hashed_password = Digest::SHA2.hexdigest(self.salt + @new_password)
    end
  end

Of note here is the salt, it’s set to a new random value every time the user changes his password, this will come in handy another day. The next method we need to implement is the authentication method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  class User < ActiveRecord::Base
    # As is the 'standard' with rails apps we'll return the user record if the
    # password is correct and nil if it isn't.
    def self.authenticate(email, password)
      # Because we salt the passwords we can't do this query in one part, first
      # we need to fetch the potential user
      if user = find_by_email(email)
        # Then compare the provided password against the hashed one in the db.
        if user.hashed_password == Digest::SHA2.hexdigest(user.salt + password)
          # If they match we return the user 
          return user
        end
      end
      # If we get here it means either there's no user with that email, or the wrong
      # password was provided.  But we don't want to let an attacker know which. 
      return nil
    end
  end

Cleanse the Logs

Finally, you need to make sure that your user’s passwords don’t get logged either, thankfully this is built right in with rails.

1
2
3
  class ApplicationController < ActionController::Base
    filter_parameter_logging :password
  end

Wrap Up

All in all using secure password hashing takes around 15 lines of code so there’s no excuse for not doing it in your applications. It’s also relatively simple so I wouldn’t suggest using a plugin if all you want is simple secure login code, those plugins come with tonnes of additional features which you may not want. I’ll follow up next week with simple secure remember-me tokens which also don’t require anything fancy.

Finally a product plug, 1Password from AgileWebSolutions is a really useful tool for generating, storing and recalling secure passwords for the myriad of websites which require logins. It’s much simpler and more secure than re-using some ‘insecure’ password on dozens of websites. It also has the side benefit of being pretty-well immune to phishing attacks.


Comments

Leave a response

  1. PaulAugust 03, 2009 @ 08:29 AM

    Thanks for this interesting article.

    “All in all using secure password hashing takes around 15 lines of code so there’s no excuse for not doing it in your applications.”

    I don’t think that is a reasonable summary. Although I completely that there is no excuse* for not hashing/salting passwords, it is actually going to be more than 15 lines of code because of people forgetting their passwords.

    I know a reset is possible (with just a few lines of code), but I have seen a lot of people prefer to have their password given to them (on the phone or in an email). I would never want this, but other people do. So you have 15-20 lines of code, plus the inconvenience of having to change your password every time you forget it. To me, that still leaves no excuse.

    Has anyone else received objections to using hashed passwords?

    *I can think of one excuse: The users know it is plain text; they know the risks (so they don’t reuse a password); and they know that the info being protected isn’t high value. This is pretty rare, but I’ve seen it.

  2. levinalexAugust 03, 2009 @ 08:33 AM

    Small thing:

    don’t use SHA256 for password hashing, use some algorithm that is designed to be slow (like bcrypt or scrypt) to make offline password cracking even more difficult. (dedicated hardware for SHA256 is really fast)

    http://bcrypt-ruby.rubyforge.org/

  3. Andrea SancioAugust 03, 2009 @ 09:23 AM

    About filtering cleansing the logs, it’s a good idea to filter :password_confirmation too (if you use it in your signup form) :)

  4. WyboAugust 03, 2009 @ 02:33 PM

    I would (and do) use the excellent authlogic gem, which does all of this for you, and much more (also can handle lost password resets), and all of this without generators, and thus with automatic updates.

    http://rdoc.info/projects/binarylogic/authlogic

    (I am not involved in authlogic in any other way than using it)

  5. Maciej CeglowskiAugust 03, 2009 @ 05:08 PM

    Use whatever algorithm you want, but put a 1 or 2 second sleep in the password check function.

  6. Matt NowackAugust 03, 2009 @ 06:23 PM

    @Maciej Ceglowski Putting a 1 or 2 second sleep in the password check function is a good idea to keep people from brute forcing through your web interface, but @levinalex’s point still stands for the correctly identified threat of offline cracking.

    Another methodology to use to thwart offline cracking is to store the password hash and the random salt as a composite value, this disguises the fact that you are using a salted hash.

    A composite salt hash value could have the first 20 characters be a random salt followed by the hash of the salted password. It complicates the verification logic but unless the person that has stolen your password data goes digging through your source code they won’t know where to find random salt, and the fact that a random salt and a password hash look the same will make the job of determining that they are a composite value are the more difficult.

  7. KozAugust 03, 2009 @ 09:53 PM

    @levinalex: Yeah, bcrypt would be a great choice, but as it’s not ‘built in’ I didn’t want to cover it here.

    @Maciej, Matt: I’d enforce that in the authentication controllers and instead of sleeping for 1or 2 seconds, just make the user solve a captcha after 3 failed logins from a given IP.

    @Paul: I don’t want to leave the impression that I’m opposed to these authentication plugins, they’re pretty sweet. But I’ve seen lots of people say “I can’t use them because I already have this huge model”, and that’s no excuse not to do things safely.

  8. KozAugust 03, 2009 @ 10:38 PM

    Also, on the matter of “resetting passwords” vs sending them out. Every time I get an email with my password I kinda freak out a little bit. Even if users would prefer that behaviour, it’s just not worth the risk.

    So that leaves resetting to some random string (using secure random or similar) or emailing a reset-link. You have to be careful with resetting a password though as an attacker could just reset everyone’s passwords and cause you a great deal of annoyance.

  9. Dan DeLeoAugust 04, 2009 @ 02:53 PM

    Another, and in my opinion better, way to make brute forcing of the database alone impossible is to have an application-wide salt and a user salt. This is shown in the Agile Web Development with Rails book. It works like Koz’ example except you do something like:

    Digest::SHA2.hexdigest(self.salt + “appsalt” + @new_password)

    It’s possible to extend this so that the application salt is kept in a YAML file or whatever. Then you can use the same strategies that you use to keep the production database password outside of version control to keep the app salt secret.

  10. PaulAugust 04, 2009 @ 10:34 PM

    Koz said, ”...I kinda freak out a little bit.”

    Me too! If I’m not feeling too lazy, I will often send the site owner an email and complain. I strongly believe it is totally unacceptable to store plain text passwords without user’s knowing it (and the associated risks).

    Generally when someone asks for the emailing of passwords, my first response is to explain why it is a bad idea. But in the end the guy paying the bills gets to decide. And he doesn’t always make the right decision.

  11. KozAugust 05, 2009 @ 02:45 AM

    @Dan: An app salt does increase the keyspace, but it doesn’t necessarily make an enormous difference to the feasibility of offline cracking. After all, once they’ve solved one they have it everywhere. It certainly doesn’t hurt.

    I was actually going to include that but I couldn’t find a single reference in my crypto-searching that made a compelling case for it.

    @Paul: I use 1password now so even if it’s plaintext it’s big, long and only for that site. I also used the genpass bookmarklet prior to that. I’ve generally found that most of my customers will do almost anything to keep a ‘secure’ tickbox, so when I tell them that plaintext pws are insecure, they tend to just accept the difference.

  12. Ben JohnsonAugust 05, 2009 @ 04:14 AM

    One of the things I would highly recommend is using the BCrypt library with a cost greater than or equal to 10. When it comes to dictionary / brute force attacks your only real defense is time and BCrypt takes more time to generate than Sha. Even better you can increase or decrease the cost to get a good balance between performance and security. But what I really like about BCrypt is the fact that I can increase the cost whenever I want and it is still backwards compatible with older passwords since the cost is stored in the hash itself. So you can increase the cost as computer get more powerful.

  13. Dan DeLeoAugust 05, 2009 @ 07:39 PM

    @koz, I would argue that there’s a tremendous difference that can be made using an app salt, but again the caveat is that it only helps if the attacker sneaks a “select * from users” into your database (e.g., via SQL injection probably). If they compromise the app or root the server, as in the Perl Monks case, the attacker can access the app salt, so in that case it’s no better than a longer per-user salt.

    Given that caveat, consider that without the app salt, the attacker needs to do the equivalent of bruteforcing a password like ”$APPSALTusers_password”. According to the wikipedia entry for password strength1, there is some theoretical evidence to suggest that breaking a truly random 128 bit password may never become feasible (except perhaps by quantum computers) before the heat death of the universe. So, if your app salt is something like SecureRandom.base64(9), then even if a user’s password was an empty string, the app salt makes it computationally infeasible to brute force the password.

    On the other hand, if the app salt doesn’t contain much entropy, you are absolutely correct, it would only add a few minutes/hours/days before the attacker recovered the app salt, after which time the rest of the password could be cracked much more efficiently.

    Anyhow, I really liked the article, it’s always a good thing to raise awareness of these security WTFs.

    1) http://en.wikipedia.org/wiki/Password_strength

  14. Tom AAugust 10, 2009 @ 05:20 AM

    @Andrea – actually you don’t need both :password AND :password_confirmation because filter_parameter_logging just converts :password to a regexp like ”/password/i” which will match either as “password” or “password_confirmation” fields.

  15. Diego RVOctober 08, 2009 @ 01:48 PM

    Can I see the archive of this blog? I can`t find the link.

    Thanks

  16. lenOctober 16, 2009 @ 10:27 PM

    Sounds like your well educated enough to know better. Yet you reused your same password and are blaming the site for your lack of security on your own behalf.

  17. Leon BreedtDecember 21, 2009 @ 06:09 AM

    @Koz:

    You’ll make crackers jobs that much harder simply by hashing multiple times (since passwords are typically short, hashing only once yields much more attackable results), in addition to salting.

    Good discussion here, tptacek makes some great contributions:

    http://news.ycombinator.com/item?id=995673

Comment