Password Security Part 2: Using Bcrypt

  

Password Security Part 2: Using Bcrypt

Note: this is part two of a three part Password Security series.

In part one of our series on password security we looked at why we should be using bcrypt to hash our passwords to ensure security against both brute force and dictionary attacks, in this article we will look at how to implement bcrypt.

Using bcrypt in many languages is really simple. It’s available in PHP, Ruby, Python, JavaScript, Erlang, Lua… you name it, it probably has an implementation. We will be looking at both PHP and Ruby/Rails.

Bcrypt in PHP

With PHP 5.5, a new password extension was added to that makes password security easy by default.

The implementation consists of four functions:

To hash a password, do the following:

$password = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 11]);

You may notice the second argument of PASSWORD_DEFAULT. This will automatically select the best algorithm at the time, as decided by PHP. Currently it is bcrypt, but this option gives you automatic forward compatible security, should bcrypt be compromised or become ineffectual in some way.

Additionally, we supply a third argument with the cost. The cost is what determines how long the password takes to generate. It defaults to 10, however this is just a general guideline and your hardware will determine what makes more sense. The difference between an Amazon EC2 m3.medium and a c3.8xlarge is obviously going to have a significant impact on how long it takes to generate a hash.

To verify a password, we do:

$user = User::findByUsername($username);
if (password_verify($_POST['password'], $user->password)) {
     // Password matches
} else {
     // Password does not match
}

The last part of the puzzle is ensuring that passwords are kept up to date. We can do this in two ways. One by using password_get_info() to check the algorithm and cost against our current settings. Or two, by using the built-in function password_needs_rehash() to check the hash against the settings automatically. By checking this on login, we can automatically rehash and store their password using the latest settings as supplied by PASSWORD_DEFAULT, or a change in the cost option:

$user = User::findByUsername($username);
if (password_verify($_POST['password'], $user->password)) {
	if (password_needs_rehash($user->password, PASSWORD_DEFAULT, ['cost' => 12])) {
        $user->updatePassword($_POST['password']);
    }
} else {
     // Password does not match
}

The password_needs_rehash() function will return true for MD5 and SHA-1 hashes also. This means that you can check prior to password_verify() and use your legacy mechanism for verification instead. Once verified, you can then update the password using password_hash(). In this way you can roll out security updates for your users whilst avoiding mass password resets that would inconvenience your users. Though, if you are using (unsalted, especially) MD5 or SHA-1, it’s probably a good idea to do a mass reset anyway.

Using Bcrypt in Ruby

For Ruby, we use the bcrypt gem which allows us to hash and validate passwords:

require bcrypt

hash = BCrypt::Password.create(password, :cost => 11)

As with PHP, you can pass in an options hash including an option to set the cost.

For validation, you can use the comparison operator:

if hash == password then
     # Password matches
else
     # Password does not match
end

Additionally, automatic support has been available in Rails since 3.0, thanks to ActiveModel::SecurePassword. To use it, first add bcrypt to your Gemfile:

gem 'bcrypt', '~> 3.1.7'

Then add a password_digest attribute, and call has_secure_password:

# Schema: User(name:string, password_digest:string)
class User < ActiveRecord::Base
  has_secure_password
end

At this point, you can then pass in either a password and password_confirmation arguments, or just the password argument alongside setting the validations argument to false:

user = User.new(name: 'Davey', password: 'password', password_confirmation: 'password')
user.save

Or without confirmation:

user = User.new(name: 'Davey', password: 'password', validations: false)
user.save

Then to authenticate, you can either call the authenticate method:

user.authenticate('password')

Or you can do it when retrieving the user from the database:

User.find_by(name: 'Davey').try(:authenticate, 'password')

This method will only return a valid user object if the password is correct, otherwise it will return false.

A Note on Cost

The role of the cost is to ensure that the passwords remain difficult to brute force even as hardware gets faster. You will need to continuously tweak your cost as your own hardware changes. However, if you are not careful and just crank it up to 11 (figuritively speaking) your passwords will take too long to generate, tying up web server threads and can easily lead to a DoS attack.

Conclusion

While we only cover PHP, and Ruby/Rails in this article, we have started a new polyglot resource to provide examples in as many languages/frameworks as possible: SecurePasswords.info. Here you will find additional examples for Node.js and Zend Framework 2, and we hope to add more in the near future.

We welcome contributions, whether it’s for your favorite language, framework, or using your favorite library — checkout the Github and contribute.

In the final part of this series, we will take a close look at the resulting hashes, and discuss what else we can do to make sure our users are safe.

P.S. What are your security best practices? We’d love to hear!

Note: this is part two of a three part Password Security series.

Free Ebook:
Should I Hire DevOps or Outsource to a Provider?

You have to invest in your infrastructure: Do you hire DevOps for this critical function, assign it to your already overworked engineers, or outsource to a provider that offers full-stack capabilities?

Should I Hire DevOps?

Davey Shafik

Comments

Subscribe Here!