Upgrading to bcrypt
Every so often, someone hacks a company and steals their database. Usually the database contains a bunch of email addresses and passwords. Two weeks ago, LivingSocial was hacked, leaking 50 million users’ data. Even companies as big as Sony aren’t immune; they were hacked in 2011 and had 77 million users’ data stolen.
As a developer, it’s your responsibility to protect your users’ data should this happen to you. Depending on how you store passwords, it can either be trivial or impossible for attackers to compromise your users’ accounts. From least to most secure, these six formats cover the majority of password storage techniques:
- Plain text
- Obfuscated (Caesar cipher)
- Encrypted (Triple DES)
- Hashed (MD5)
- Salted and hashed
- Computed from a key derivation function (bcrypt)
It’s unequivocally better to store passwords securely. There are no downsides and brute-force attacks become orders of magnitude harder. Any greenfield project should use bcrypt (or another key derivation function like PBKDF2 or scrypt).
But what about existing projects? Upgrading to a new scheme isn’t trivial because current passwords need to be migrated. In fact, the user shouldn’t be able to tell that anything changed. The old system should be silently deprecated and replaced with the new one.
Here’s how to do exactly that with Rails 3.2.13 and bcrypt-ruby 3.0.1 on Ruby 1.9.3-p392.
Let’s say you’re in the worst possible scenario: you store passwords in plain text. Hopefully you don’t actually do this, but it makes this example a lot simpler. The same principles work regardless of how you store your passwords.
Assuming a straightforward user model, you might authenticate users with a class method. All it does is try to find the user, then compare the passwords. If everything checks out, it returns the user. In all other cases, it returns
We want to jump straight to the best case scenario and start using bcrypt. Three things are necessary to get that done: add another field to the user model; add a handful of new methods; and modify the authenticate model.
Up first is adding a new field to the user model. We need to store the derived key bcrypt generates. A simple migration takes care of this step:
Now we need a couple utility functions. They’ll allow us to see which users use bcrypt, set the password, and compare strings against it. These all require the bcrypt-ruby gem, so add
gem 'bcrypt-ruby' to your Gemfile.
Lastly, the authenticate function needs to be modified. It should compare using bcrypt if the user has been updated. If they haven’t, it should compare using the old method.
Once a user authenticates using the old method, it should generate a bcrypt hash for them so it’ll use that next time. In addition, it needs to delete data stored by the old method. If it doesn’t, an attacker could just focus their efforts on the legacy data.
At some point you’ll want to remove everything that’s still stored in the old format. For users that haven’t updated yet, a new password must be generated. You can either email it to them or they can rely on your password recovery service.
Depending on how your tests are set up, switching to bcrypt could slow them down. Changing the work factor is the easiest way to avoid this slowdown. The next version of bcrypt-ruby will support setting the cost with
BCrypt::Engine.cost = x. For the time being, monkey patching is the way to go. Drop this into
Upgrading a legacy system to use bcrypt isn’t that hard. You should do it sooner rather than later. In the unlikely (but entirely possible) event of a database leak, your users’ passwords will be protected.
[Originally posted to my blog.]
Taylor Fausak was born in California, but he got to Texas as soon as he could. He studied Computer Science at the University of Texas at Austin before entering the wild world of software development. After a brief stint at Cisco, he started his career at Famigo working on all aspects of web development. Then he swapped his Django experience for a chunky slice of Rails bacon and joined OrgSync in the fall of 2012. When he's not slinging code around, he likes riding bikes, playing Magic, and throwing frisbees.
comments powered by Disqus