We all know. We should never ever store a plaintext password in a database. If a hacker gains access to your data you will be in serious trouble. There are many ways to protect your data, but at least you should make sure your passwords are not readable. In the past we did this by some simple hashing, but modern computers are so fast it is easy to do some password cracking. In time it even gets easier because processors are becoming faster and faster. Another disadvantage: simple hashing will reveal some records with the same passwords. These are often the easiest to guess or crack by brute force. So we need something better.
Coldbox has a nice little module called bcrypt, which is just a wrapper to the jBcrypt java library which is a Java™ implementation of OpenBSD’s Blowfish password hashing code. Wikipedia has a nice description of the bcrypt password hashing algoritme. Bcrypt hash some strong points:
- generating a hash for the same string will always return different results
- comparing a password candidate with the stored hash is relatively slow, which makes brute force attacks harder.
- the hash can be generated with different workfactors. The higher the workfactor, the more time it takes to compare your hash with a password candidate. By increasing the workfactor in time you can account for faster processors, so brute-force attacks remain unattractive.
Installation of the module is quite simple in commandbox:
box install bcrypt
For generation of bcrypt hashes three functions are important
- generateSalt( [workFactor]). This generates a salt for the hashing. A workFactor is optional, if you don’t specify one, it will take the one in your module settings. The higher the workFactor, the longer it takes to compare a password candidate to your hash. This is exponential. Increasing the workfactor by one will double the comparison time.
- hashPassword( password, [workFactor),[salt]). The workFactor is optional, if you don’t specify one, the module setting will be used to generate a salt. If you specify the salt parameter (which already has an implicit workfactor), the workfactor parameter will be ignored. If you are happy with the defaults, the hashPassword will generate a hash with a workFactor of 12. Typically you will store your password hash somewhere in persistent storage.
- checkPassword( candidate, bcryptHash ). This function will compare a password candidate string with the stored hash. It can do so, because the has will contain some information such as the hashing mechanism, the workFactor and the salt. With these parameters the checkPassword function can make the comparison.
So how does a bCrypted string look like? I will hash the string ‘simple’ for you.
// var crypt=getInstance("bcrypt@BCrypt"); // var myHash12_1 =crypt.hashPassword("simple"); Factor 12: $2a$12$8RQwfyiSHf.Ef.COeOFQKOdMLTqj/ARBuOJpb9Yc3T3ITJlRrOn32 // var myHash12_2 =crypt.hashPassword("simple"); Factor 12: $2a$12$ZsQxzZLFEfApKfAzPcxdreVC4y85evOzERuSiO2slfQPZy4sMtSjS //var myHash15 =crypt.hashPassword("simple",15); Factor 15: $2a$15$Asmn/CTHtijPL058jBmjv.2J8NgrKHOVSyXy9lf4eXzPQTSgWG.DG
As you can see, although I hashed the same string twice with the same workFactor the results are different. There are some simularities in the beginning of the string, which holds some type and workFactor information. A bcrypted string has the following format
$2a$15$Asmn/CTHtijPL058jBmjv.2J8NgrKHOVSyXy9lf4eXzPQTSgWG.DG \__/\/ \____________________/\_____________________________/ Alg Cost Salt Hash
From the first 3 characters you can determine the hashing algoritme, in our case the
$2a is for bcrypt hashing. The
$15 is for a cost of 15, lowering or increasing by one will halve/double the computation time, so my $15 will increase time by a factor 23 (8). The last two parts both have a fixed length and consists of the salt and the hash. Both parts are base64 encoded. When the bcrypt module is returning the salt, it not only returns the real salt, but also the algorithm and the cost. This way all information for calculating a hash will be contained in only two function parameters: the password and the salt. Only the password is required for the hassPassword function because a random salt will be generated automatically.
So each hashed password has the same length, and cost, algoritm and salt can be determined from the full bcrypted string. This is what makes it possible to compare a password candidate to the bcrypted string. When I do this for the encrypted strings with costs 12, 12 and 15 my results are:
var start=gettickCount(); writedump(myHash12_1); crypt.checkPassword("simple", myHash12_1); writedump("Time (workFactor 12): #getTickCount()-start#"); var start=gettickCount(); writedump(myHash12_2); crypt.checkPassword("simple", myHash12_2); writedump("Time (workFactor 12): #getTickCount()-start#"); var start=gettickCount(); writedump(myHash15); crypt.checkPassword("simple", myHash15); writedump("Time (workFactor 15): #getTickCount()-start#");
So as you can see, a workfactor of 15 is a bit high. The default workfactor of 12 will do nicely with checkPassword times of around 250 milliSeconds.
Actually using bcrypt is quite easy. Now you know why you should use it (or a simular library) there are a few questions left which I will adres in a future post.
- where are we implementing bcrypt? In a handler, a service or some model object?
- how are you migrating your users to bcrypted passwords
- can I detect bcrypted strings, so I know I don’t have to encrypt them again, or (spoiler) are there simpler methods?
Leave a Reply