Taking Password Storage Up A Notch

Protect your users, take password management seriously.

There are too many databases in the world with completely unencrypted passwords. Even when databases have encrypted passwords, they typically do not implement protection that is strong enough to keep them from being stolen, and used maliciously. With the advancements in computing technology, we now need another approach to store passwords and keep them safe from prying eyes. Fortunately, you don't have to be a cryptography expert to use good password protection.

Passwords that are only hashed once are vulnerable to brute force, dictionary attacks, and rainbow tables. To combat these attacks, salt has been added to hashed passwords. However, with growing computing power, we need to go one step further. To protect our users properly, we need to increase the time it takes to encrypt passwords.

Two examples of alternate methods for password storage that solve the time problem are PBKDF2 and BCrypt. PBKDF2 stands for password based key derivation function 2. It is a replacement for an older function and is most notably used by iOS, Android, and LastPass. BCrypt is a technology used on secure operating systems such as OpenBSD. Here are the different implementations in Rails. First, the wrong way.

Plain Text (The Wrong Way)

Here is what a normal Rails model might look like without password encryption. All of the logic is in the controller to keep things simple and get the point accross.

1 class User < ActiveRecord::Base
2   attr_accessible :email, :password, :password_confirmation
3 end

And this is what a normal Rails controller could look like without password encryption. The fields in the table are email, and password_digest.

 1 class UsersController < ApplicationController
 2   def index
 3   end
 4 
 5   def new
 6     @user = User.new
 7   end
 8 
 9   def create
10     @user = User.new
11     @user.email = params[:user][:email]
12     @user.password_digest = params[:user][:password]
13     if @user.save
14       redirect_to @user
15     else
16       render :new
17     end
18   end
19 
20   def show
21     @user = User.find(params[:id])
22   end
23 
24   def authenticate
25     user = User.find_by_email(params[:email])
26     if user && user.password_digest == params[:password]
27       redirect_to user
28     else
29       render :index
30     end
31   end
32 end

Now let’s see what these would look like with enhanced encryption methods and find out how easy it is to add additional security to your application.

PBKDF2

For Rails users, there is a gem called pbkdf2 available that will get you started right away. Put it in your Gemfile and run bundle install. In your user table, ensure you have fields for username, derived key, and salt.

Salt is a randomly generated set of bytes that is appended to an encrypted password. This is done to protect against rainbow table attacks. When a new user creates an account, generate a unique salt for that user. After the salt has been generated, a key can be derived with the user’s password.

The PBKDF2 model is the same as it would be in a clear text implementation.

1 class User < ActiveRecord::Base
2   attr_accessible :email, :password, :password_confirmation
3 end

The controller, on the other hand, has a few differences.

 1 require 'pbkdf2'
 2 
 3 class UsersController < ApplicationController
 4   def index
 5   end
 6 
 7   def new
 8     @user = User.new
 9   end
10 
11   def create
12     @user = User.new
13     @user.email = params[:user][:email]
14     @user.salt = make_salt
15     @user.password_digest = encrypt(params[:user][:password], @user.salt)
16     if @user.save
17       redirect_to @user
18     else
19       render :new
20     end
21   end
22 
23   def show
24     @user = User.find(params[:id])
25   end
26 
27   def authenticate
28     user = User.find_by_email(params[:email])
29     if user && user.password_digest == encrypt(params[:password], user.salt)
30       redirect_to user
31     else
32       render :index
33     end
34   end
35 
36   private
37 
38   def encrypt(clear_text, salt)
39     derived_key = PBKDF2.new do |key|
40       key.password = clear_text
41       key.salt = salt
42       key.iterations = 10000
43     end
44     return derived_key.hex_string
45   end
46 
47   def make_salt
48     return SecureRandom.hex
49   end
50 end

The encrypt function will return a string that looks something like this:

0e276cb8e5644a324b6d855862fc236242c0767b2fcd9ee58ab915e160a0c436

Store the result in the derived key field of your user table. The generated salt should be stored in the salt field. Keep in mind that the salt isn't meant to be a secret, so storing it in the same table as the password is okay. It is, however, meant to be unique and randomly generated.

Notice the number of iterations given to the PBKDF2 block. Ten thousand iterations is the current suggested guideline but understand that this can be set to accommodate specific performance needs. As the number of iterations goes up, the processing power required to authenticate users goes up. However, this also increases the time required for each and every password cracking attempt, which is what we want.

To authenticate the user, just run the same function with the salt stored in the table and the password given by the user. If the result matches the derived key that was stored in the table when the account was created, the user is authenticated. Now let’s look at the BCrypt implementation.

BCrypt

The beauty of BCrypt is that Rails 3 does most of the work for you. However, there a few key steps that must be done to use it. First, you need to put bcrypt-ruby in your Gemfile and bundle install it.

Next, you must make sure that a field called password_digest is in your user table with a string type. You'll also need some sort of username field as well, such as an email address. You will not, however, need a salt field. This is an advantage to BCrypt in my opinion.

The next step to utilizing Rails 3's built in BCrypt support is to put has_secure_password in your user model as illustrated below.

1 class User < ActiveRecord::Base
2   has_secure_password
3 
4   attr_accessible :email, :password, :password_confirmation
5 end

You're now ready to allow users to create accounts. The UsersController would look something like this. Again, you can move the logic to a better place, this is a simple example.

 1 class UsersController < ApplicationController
 2   def index
 3   end
 4 
 5   def new
 6     @user = User.new
 7   end
 8 
 9   def create
10     @user = User.new(params[:user])
11     if @user.save
12       redirect_to @user
13     else
14       render :new
15     end
16   end
17 
18   def show
19     @user = User.find(params[:id])
20   end
21 
22   def authenticate
23     user = User.find_by_email(params[:email])
24     if user.try(:authenticate, params[:password])
25       redirect_to user
26     else
27       render :index
28     end
29   end
30 end

When the user has been created, the generated password digest will look something like this:

$2a$10$VF.56hJoO/pjarIWT/xZB.cixr.Bz4B152t16WIbFhibCyDn/g9ue

BCrypt stores the salt along with the derived key together so you don't have to mess with it. The salt is taken care of for you and the digest is stored in a single field in the database. The iteration count is also stored, making this algorithm easily scalable as computational speeds increase.

Wrap Up

As you can see, implementing solid password practices is quick and easy. There is very little extra code added to the plain text version, to get the PBKDF2 or the BCrypt version. While I've focused on Ruby on Rails in these examples, implementations are available for many other languages that are ready to be used. C#, Java, and Python are just a few examples.

Databases full of usernames, emails, and passwords are being stolen at an alarming rate. BCrypt and PBKDF2 are two ways to securely store passwords and protect your users’ identities. Single hashed and salted passwords are not enough any longer. Use one of these methods and begin increasing your iteration count today.

Adam Gooch was an 8th Light Software Craftsman.