First, let us run through a couple of observations in Laravel 4:
return Hash::make('test'); $2y$10$ooPG9s1lcwUGYv1nqeyNcO0ccYJf8hlhm5dJXy7xoamvgiczXHB7S
return Hash::make('test'); $2y$10$QRgaiS6bpATKKQeT22zGKuHq.edDfXQc2.4B3v.zaN.GtGwoyQuMy
return Hash::make('test', array('rounds'=>12)); $2y$12$Az4ZMmEhUxYQ3CcgfnaTt.C1MYxmFfjNxpjgPtye0uKoMBnirw8TC
These are the results returned for me.. of course, you will get different results. But the takeaway points are:
1. Hash make returns a different hash each time. This is quite curious.
2. The output is always a 60 char string
3, The initial characters of the hash are metadata (first 7 chars)
This blog post will attempt to demystify some of the inner workings that cause Hash::make() to behave this way. So, how does Laravel do this? how then is the password check performed? and finally what is the advantage that this offers?
Internally, Hash::make() encrypts using the bcrypt function and Blowfish algorithm. For php>5.5, password_hash() and password_verify() functions are used. For previous php versions, a compatibility library irc_maxell/password_compat is pulled in by composer. In fact, reviewing the source code of the password_compat library provides a lot of insight into the inner workings of password_hash() and password_verify().
According to the php documentation (http://www.php.net/manual/en/function.crypt.php), “Blowfish hashing with a salt as follows: “$2a$”, “$2x$” or “$2y$”, a two digit cost parameter, “$”, and 22 characters from the alphabet “./0-9A-Za-z”.”
So, in our trial run 1 above,
$2y$ represents use of blowfish algorithm with salt
10 is the default “cost” factor
A 22 character “salt” is (randomly) generated and appended to the previous two components
this is followed by the encrypted password.
The php crypt function (that is internally used to implement bcrypt) is then called :
where $password is the string to be hashed, and $hash is the concatenated value of “$2y$”.”10″.”22 random characters from 0-9, a-z, A-Z”.”$”. This function returns the 60 character hash string associated with the password.
The cleverness of this is that the algorithm, salt and cost are embedded into the hash and so can be easily parsed out into individual components for reconstruction/verification (Please see relevant sections of the php crypt source code at https://github.com/php/php-src/blob/master/ext/standard/crypt.c#L258). Because of this, you don’t need to store the salt/cost separately in a database table.
For checking the password (wrapped by password_verify() for php>5.5), an internal function semantically equivalent to :
return crypt($password, $hash)==$hash;
is used. The original hash that was generated by the encryption is passed to the function (this is key). The supplied password is salted and run through the crypt function to generate the original hash (provided the same password is used). Note that internally, the crypt function only cares about the first 29 characters of the passed in hash (7 metadata+22 salt). Remember that the crypt function implements a one-way hash – there is no way to retrieve the password from the encrypted hash. The only way to verify password equivalence is to hash it using the same salt and compare the results. Both the Hash::check() and Auth::attempt() methods in Laravel run the same check.
The conventional method of using a md5 or sha1 to generate password hashes is insufficient for modern security requirements. Due to the advancement in computation power, it has become trivial to use a Rainbow Table (http://en.wikipedia.org/wiki/Rainbow_table) to crack passwords stored using md5/sha1 hash. The use of bcrypt function avoids this vulnerability. So, you now have a one-way hash function that is both secure and easy to implement.