Please use bcrypt to store your passwords

Posted by Guillaume Maury
on Dec 02, 08

Update: If you want to use bcrypt with merb-auth, I have made a patch available at http://merb.lighthouseapp.com/projects/7433/tickets/1113-bcrypt-mixin-for-merb-auth-more

Currently most people on merb use sha1 hashes and salt the passwords with a nounce to store it. It’s a good practice, it protects you from the reddit embarrassment where a database full of plaintext passwords got leaked because, apart from brute-forcing, there is no way to reverse the hash to find the password. And by salting your hash with a nounce you protect yourself against from pre-generated rainbow tables. So all is good, right?

Well mostly, but why use md5 or sha1? They are made to be fast… Cryptographic hash functions are fast by design, as the buiding blocks of all cryptographic systems, they need to be. And of course, since they are used repeatidily, it makes sense to optimize hardware for it… So it’s possible to use something like nsa@home http://nsa.unaligned.org/ made of FPGAs that can search the full 8-character keyspace (from a 64-character set) in about a day for 800 hashes concurrently….

To attack a password scheme, you would normally use an incremental cracker (a rainbow table cracker is not much use if you the passwords are salted with a nounce) and those cracker rely on the speed of the hashing operation, the faster it is to hash your password, the more combinations you can try and the weaker your password scheme is.

So what can you use instead? You can use BCrypt an adaptive hashing scheme

Why?

  • It’s made to be computationally expensive by using blowfish block cypher that is slow to set up.
  • You can configure the cost of the hashing function to make it slower

Currently the cost is set to 10 by default but you can change it and make it higher as hardwares speed go up… All the password using the old cost will continue to work (bcrypt-ruby stores the cost along with the password) and new passwords will have a higher cost.

Some quick benchmark:

require 'sha1'
require 'bcrypt'
require 'md5'

Benchmark.bm(20) do |x|
  x.report("BCrypt (cost = 12):") { 500.times { BCrypt::Password.create("mypass", :cost => 12) } }
  x.report("BCrypt (cost = 10):") { 500.times { BCrypt::Password.create("mypass", :cost => 10) } }
  x.report("BCrypt (cost = 5):") { 500.times { BCrypt::Password.create("mypass", :cost => 2) } }
  x.report("md5:")  { 500.times { salt = MD5.hexdigest("--#{Time.now.to_s}--username--"); MD5.hexdigest("mypass-#{salt}") } }
  x.report("Sha1:") { 500.times { salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--username--"); Digest::SHA1.hexdigest("mypass-#{salt}") } }
end

#                           user     system      total        real
# BCrypt (cost = 12): 250.140000   5.130000 255.270000 (276.009547)
# BCrypt (cost = 10):  62.680000   1.230000  63.910000 ( 69.320089)
# BCrypt (cost = 5):    1.090000   0.020000   1.110000 (  1.185847)
# md5:                  0.010000   0.000000   0.010000 (  0.008147)
# Sha1:                 0.000000   0.000000   0.000000 (  0.008906)

The point is that you don’t care about the time needed to hash a single password in your system it’s not a bottleneck (0.12 seconds on my mac) but attacker will…

I’m currently available for hire on a contract basis.

blog comments powered by Disqus