Using bcrypt for passwords in CakePHP
CakePHP uses salted sha1
hashes for passwords by default, and has for a while. There has been some talk on the mailing list lately of switching the default hashing to something more secure, such as bcrypt. I think this is a great idea, and will find its way into CakePHP in a future release. Providing a reasonanle upgrade experience is the biggest problem to solve, if the default hashing strategy was to change. One option is to silently upgrading passwords. I’m not a fan of this approach as it has more room to go wrong, and possibly corrupt data. Another option, allowing developers to stick with sha1
if they have passwords hashed with it is a safer and probably better overall option.
While bcrypt is not part of CakePHP just yet, I wanted to see how difficult it would be to start using bcrypt today. Turns out it was pretty simple. Getting bcrypt working only required a subclass of FormAuthenticate
and a two line change to the User class. The subclass implements the modified password hashing, and looks like:
- <?php
- App::uses('FormAuthenticate', 'Controller/Component/Auth');
- class BcryptFormAuthenticate extends FormAuthenticate {
- /**
- * The cost factor for the hashing.
- *
- * @var integer
- */
- public static $cost = 10;
- /**
- * Password method used for logging in.
- *
- * @param string $password Password.
- * @return string Hashed password.
- */
- protected function _password($password) {
- }
- /**
- * Create a blowfish / bcrypt hash.
- * Individual salts could/should used for increased security.
- *
- * @param string $password Password.
- * @return string Hashed password.
- */
- }
- }
Because bcrypt needs a 22 alphanumeric salt, I decided to trim the application’s salt value. After creating the bcrypt authenticate class, I had to update my controller components array:
I also had to update my User model’s beforeSave
callback:
That was all of the changes I needed to make. Keep in mind that this requires the most current 2.0. There was a recent change to add the _password
method that makes this extension so straight-forward. While I think having bcrypt in CakePHP itself is important and should happen, there is nothing preventing you from using it today if your application needs stronger hashes than what sha1 can provide.
Edit I removed the md5()
salt as jdbartlett pointed out this is not a good approach.
Thanks for the article – it seems fairly simple for 2.0.
Is it possible to use bcrypt for password encryption in 1.3? How much more work would it be over using it in 2.0? I am new to cakePHP and not sure I want to upgrade to 2.0 just yet.
Paul West on 9/28/11
MD5? Mark, no!
When the salt is derived from the password rather than being random, it can only weaken the resultant bcrypt hash (unless the system used to derive the salt from the password is actually stronger than bcrypt itself). The salt should be 16 bytes of unique randomness, and the bcrypt hash includes the salt precisely so that it can be used in future when checking the password.
BcryptFormAuthenticate::hash is returning a string that includes part of an MD5 hash of the original password, which is bad. I think you’d be better off using Security.salt than risking even partial MD5 exposure.
I’m just starting work on a similar library, but overriding the _findUser method instead of the _password method. My plan is to find the user record with the matching username, check its password using the stored (randomly generated) salt, and return false if it doesn’t match. Admittedly, it’s less concise than straightforwardly overriding the _password method, but I don’t want to undermine bcrypt itself.
jdbartlett on 10/4/11
How about `substr(uniqid(’‘, true), 0, 22)`?
LancerForHire on 10/4/11
@LancerForHire You won’t be able to stitch a solution like that directly into Mark’s code. Once you’ve generated a genuinely random salt, you’ll need access to the bcrypt hash string (which includes the salt) when you want to check the password. The problem is, BaseAuthenticate assumes that password generation requires only one parameter — the actual password — instead of 2 or more parameters per bcrypt.
If you look at BaseAuthenticate::_findUser, you’ll see what I mean: it’s written to check the database for a row where the password matches a given value. Since you don’t know the salt at the time you’re doing the checking, you need to override _findUser to find the row by username alone, and then (once you’ve retrieved the record by username) use the salt and configuration options embedded in the hash to create a new hash from the password against which to check the stored hash.
Unfortunately and embarrassingly, my current project is probably going to be hosted on a PHP 5.2 server, so bcrypt isn’t an option for me, but instead I’m using a PBKDF2 based system that’s superficially similar to bcrypt. Here’s the authentication object I’ve created:
https://gist.github.com/1263248
HashFormAuthenticate::checkPassword takes two parameters — the password, and the hash to check it against. Taking the full hash string (instead of just the salt) means you can deal with other parameters (like the number of rounds) as well as the salt.
I recommend checking out phpass if you’re using bcrypt. It includes some handy wrapper methods, and also a secure salt generator that’ll provide much less predictable salts than using uniqid. (uniqid really isn’t intended for security use — John Haugeland’s comment at PHP docs explains why.)
jdbartlett on 10/4/11
jdbartlett: Thanks for pointing out the problem in using md5 for a salt. I wasn’t aware that the salt was stored as part of the hash. Since there isn’t an easy way to pair unique salt’s with a password, I’ve updated the post to use Security.salt. That at least removes the exposure to md5 sums. Overriding
_findUser
would give you the ability to use per user salts though.mark story on 10/7/11
What about the content of Security.salt? Based on documentation, the salt must be something that matches “./0-9A-Za-z”.
This mean I couldn’t use something like “7[p5~s`~k|KL@VYUyM)$7” for Security.salt, right?
CRYPT_BLOWFISH – Blowfish hashing with a salt as follows: “$2a$”, a two digit cost parameter, “$”, and 22 digits from the alphabet “./0-9A-Za-z”. Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithmeter and must be in range 04-31, values outside this range will cause crypt() to fail.
Thiago Belem on 1/14/12
i am currently working in a cakephp project which is a social network app in action.Your blog is helping me a lot .I find it awesome than cakephp book. Can i have my password as decrypted so that i can mail it to users in my site and whenever they request for it.Thanks in advance.I had tried book.cakephp,but no escape from this requirement.Please help me
jafar on 2/3/12
Jafar,
Please don’t pursue your current plan. If the user forgets their password, randomly generate and email them a temporary password to regain access to their account.
It’s VERY important that you use a secure hashing algorithm like bcrypt (the one Mark suggests) to store passwords. The goal is actually to PREVENT password decryption while still storing a value you can compare for logins.
When a breakin occurs on any site, data thieves head first to the users table. If you use bcrypt, those data thieves won’t be able to decrypt the password data you’ve stored. Because of your foresight, hackers won’t be able to use that data to break into and ruin your users’ accounts. Since some users use the same password for multiple accounts on the internet, your decision to use bcrypt may save them from suffering breakins elsewhere, too.
For more information about password security, I recommend this post by Coda Hale:
http://codahale.com/how-to-safely-store-a-password/
jdbartlett on 2/3/12
jafar: Never ever ever ever store a password in a way you can get the original plain text out of it. You want the ability to reset a password instead. Usually you send people an email with a short lived link that allows people to change their passwords to a new value.
mark story on 2/5/12
I don’t know what your strategy was to silently update the passwords, but I sat with a similar problem the other day when thinking of ways to integrate users from another existing application into a new app with a different salt. My solution was to silently update the user’s password upon login.
Using CakePHP 2.x…
What would happen is the user logs in with his username and password. We try to log him in. If the username and password is invalid, an Exception is thrown by the AuthComponent. We catch the Exception and we try to log the user in using the old salt and old hashing algorithm. If this login is successful, we hash the password from the request object (which is in cleartext) using the new salt and algorithm and save it. Next time the user logs in, there won’t be an Exception.
I didn’t actually implement this yet, but what do you think about the solution?
Werner on 2/24/12
I’ve tried this in the latest CakePHP and it doesn’t seem to be working correctly.
While it works well with creating users, the hash function doesn’t seem to be used when logging users in, so it always returns invalid password.
I’ve followed the post above exactly, is there anything I might be misssing?
Alex on 5/28/12
Ignore my last comment, I was overwriting the BcryptForm component in my users controller which is why it wasn’t working.
Alex on 5/29/12
Mark, I see that the “BcryptFormAuthenticate” class’ “hash” method has a comment that says “Individual salts are used to be even more secure”. However, looking at the code, I don’t see how you figure that the salt is individual. It’s just the first 22 characters of the application’s salt value. I didn’t see any code that makes the salt unique for each user. Am I missing something?
Nick on 7/24/12
Nick: That comment should say individual salts would. I’ll update the post. Also as of CakePHP 2.3, I would suggest using the new built-in features to generate bcrypt hashes.
mark story on 7/24/12
Thanks, Mark. That makes a lot more sense. It looks like you left out a word in the new comment, though.
Also, thanks for the tip on CakePHP 2.3!
Nick on 7/30/12
- thank you for this tuto – i used Security::hash , and i want to display the password, how can i do it ?
Hicham on 4/17/13