Password Security Part 2: Using Bcrypt
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:
- password_hash() Create a new password hash
- password_verify() Confirm a given password matches the hash
- password_needs_rehash() Check if a password meets the desired hash settings (algorithm, cost)
- password_get_info() Returns information about the hash such as algorithm and cost
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!
Share your thoughts with @engineyard on Twitter
OR
Talk about it on reddit