2012-08-09

The Modern Way to Store Passwords - BCrypt

When authenticating users, you should never store their password value directly. The proper way is to salt and hash before storing.

Most hashing algorithms are designed to take a large amount of data such as documents and quickly return a hash. For storing passwords, you want the opposite: small data, and slow (strong) hashing.

Recognizing this different need, BCrypt takes a different approach. BCrypt uses the very strong but slow Blowfish cipher to do the hashing. BCrypt generates a random salt value to be added to the password, changing on each usage. Furthermore, and most importantly, BCrypt is adaptive. That is, as computing technology speed increases, you can increase the the strength of BCrypt by passing an increasing number. Though oversimplifying, you can think of the number as indicating the number of iterations of hashing and re-hashing. As computers get faster in the future, you can increase the strength of your hashing by passing a higher numbers.

For more info, read the original paper by  Niels Provos and David Mazieres. Read a brief overview by Coda Hale. Read this entertaining article by Thomas Ptacek.

Postgres 9 has a crypto module that includes a "crypt" command that seems to implement BCrypt. Here's a example of calling that command, where 'bf' means Blowfish:
    SELECT crypt('YourPasswordGoesHere', gen_salt('bf', 10));

Returns a hash value like this:
     $2a$10$Tq0dC2EDyqKhFbjQpu42b.Hzvg4dntLSZPFfvacmzKqR0E6uIRUyG

Increasing that number slightly dramatically increases the burden on a comupter. Here's some results of calling that in Postgres 9.0.4 on Mac OS X 10.6.7 on a MacBook with a 2.4 GHz Core 2 Duo.
  • 6 ≈ 40 milliseconds
  • 8 ≈ 130 ms
  • 10 ≈ 500 ms
  • 12 ≈ 1,600 ms
  • 14 ≈ 7,000 ms
  • 16 ≈ 28,000 ms (half a minute)
  • 21 ≈ 1 million ms (17 minutes)
During this time, the calculation took about 80-100% utilization of a CPU core.

BCrypt has been implemented in many languages, including C, Java, Python, Ruby, and more.

An implementation is bundled with Postgres, at least when using the installer for Mac OS X provided by EnterpriseDB. At least it seems to me that you are getting BCrypt if you specify the 'bf' argument, but the docs are not explicit -- Please correct me if I'm wrong.

To use this in Postgres,  you must enable the "pgcrypto" functions, by copying and executing the SQL found in the "pgcrypto.sql" file found someplace such as this:
    /Library/PostgreSQL/9.0/share/postgresql/contrib/pgcrypto.sql
Open that file, copy and paste to an interactive SQL window in the pgAdmin app. Actually, you only need the first third of that file's SQL, down to but not including the "pgp_" functions.

    3 comments:

    1. Hi,

      How would you compare the password since each time a new hash is generated?

      ReplyDelete
      Replies
      1. SELECT stored_passwd FROM passwd_table WHERE user_name = 'SomeUser' AND crypt('SomePassword', stored_passwd) = stored_passwd;

        Delete
    2. I like what bcrypt offers, yet concerned that it could make a server more susceptible to denial-of-service attacks. I'm wrestling with some ideas on how to reduce that particular risk:

      http://www.hashcollisions.com/2011/06/can-bcrypts-computational-expense-be-reduced-on-the-server-side/

      ReplyDelete