Best method for storing and validating login credentials

Hello,

I’m relatively new to PHP. Currently I’m using md5() to hash and store passwords. Something like:

$salt = 'SomeSaltString'; //fixed salt
$password = 'SomePassword';
$pass = md5($salt . $password);

According to the PHP manual, crypt() with blow fish is the way to go [unless you think my md5() hashing method above is sufficient]:

$hash = crypt('SomePassword', '$2a$07$StrThatIs21Characters$');
//output: $2a$07$StrThatIs21Characters.GDZ.DtvzhBXbOr3S5W68Rh5PFgnomoq

However, I don’t quite understand how to implement this correctly. A few questions:

1]. $2a indicates strength level?
2]. $07 something to do with round trips? Please elaborate further on this…
3]. StrThatIs21Characters$ - salt needs to be 22 chars in length including the $. Should this be a fixed salt?? I’m getting conflicting info on this…

These are the important implementation questions:

4]. What part of the output should be stored in the database [in the password column]?
5]. What data type best suits the password column? Perhaps nvarchar?
6]. How should a login attempt be verified when using this method?

Many thanks!

Just an example, would this be fine for question 6 - assuming the entire output from the post above [//output: $2a$07$StrThatIs21Characters.GDZ.DtvzhBXbOr3S5W68Rh5PFgnomoq] is stored as the password. [Note: I wouldn’t do a password comparison with the database like this]:

$salt = ‘$2a$07$StrThatIs21Characters$’; //fixed salt
$userPassword = $_POST[‘password’];
$hashedPassword = crypt($userPassword, $salt);
$databasePassword = //get database password

if ($hashedPassword == $databasePassword) {
echo “Password verified!”;
}

I would suggest using PHPass for password hashing. It uses bcrypt and automates hashing, salting, and stretching of passwords. The script is at: http://www.openwall.com/phpass/

Basically, you hash the password, store the hash in your DB. Then when a user logs in you hash the supplied password and compare that hash with the one stored in the DB.

The same site has a great in-depth tutorial on how best to do this: http://www.openwall.com/articles/PHP-Users-Passwords

This might have been a good technique a few years ago but nowadays it is possible to crack such passwords on cheap hardware. The main problem with this technique is that hashing to md5 (or any other hash) is very fast and if your algorithm is fast then brute force attack can be performed fast. Here is a good article about cracking passwords with modern GPUs: http://www.troyhunt.com/2012/06/our-password-hashing-has-no-clothes.html

Yes, use blowfish. The main advantage of this algorithm is that you can adapt its ‘cost’ - how many hashing iterations will be done and in this way how much the process will be slowed down. If your algorithm is slow then it may take too much time for a potential cracker to bother with brute forcing.

No, $2a$, $2x$ and $2y$ all indicate that blowfish should be used. Generally, $2a$ is fine but there was some weakness discovered in PHP versions earlier than 5.3.7 so $2x$ and $2y$ were introduced. If you use 5.3.7 or later then simply use $2y$, otherwise use $2a$.

This is the cost factor. The higher the number the more hash iterations will be performed and the longer the process will take. Write a small test script that will measure how much time crypt() will take to execute and adjust the cost factor to find the desired balance. I’d say if it takes somewhere between 0.1 and 0.5 seconds then it’s fine. Later as your site is moved to faster hardware you can always increase the cost factor. I have found that on my shared hosts 09 or 10 is good.

No, this string is not 21 characters plus $, it’s 22 characters and $ is not required at the end. As the manual says:

22 digits from the alphabet “./0-9A-Za-z”

This is the random salt that you should generate.

Just store the whole output of the crypt function. No need to store the salt separately since the salt is included in the output.

Yes, varchar is fine. I don’t know SQL Server so I don’t know about nvarchar, but any variable-length column capable of storing ascii string data will be fine. No need for any unicode or other encodings here.

First, this is what I use to hash the password:


$random_str = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
$salt = substr(base64_encode($random_str), 0, 22);
$salt = str_replace('+', '.', $salt);

$hashed_password = crypt($password, '$2y$09$'.$salt);
// $hashed_password is what should be stored in the db

mcrypt_create_iv() returns binary data and is a very good simple function to get a random string for the salt because it provides better randomness than rand() and mt_rand(). base64_encode() gives us almost the right range of allowed characters for the salt, we only need to change ‘+’ to ‘.’.

And this is how I check if entered password is correct:


if ($hashed_password_in_db === crypt($password_entered_by_user, $hashed_password_in_db)) {
	// password correct
	// ....
}

As you can see the result of crypt can be used as a salt hence no need to store the salt separately. This method also allows you to change the cost factor for your passwords and even change crypt() hashing algorithms and still you can validate all passwords in the same way.

I don’t see why use and learn an external library for something that can be easily and efficiently achieved with a few lines of native PHP code, unless you want compatibilty with older PHP versions and installations where crypt algorithms are limited, but for any new PHP version I don’t think it’s worth it.

When it comes to things like security, I’m a fan of the philosophy “don’t re-invent the wheel and roll your own when there are already very solid solutions.” If someone who is an expert when it comes to password hashing has created a library that works well enough to be adopted by a good chunk of the community, it would be better IMO to use that instead of rolling my own.

It would be similar to something like data validation/sanitizing. Why roll your own using regex and other methods when there is filter_var?

It cuts down on the potential that you’ll make a mistake or forget certain aspects.

It also cuts down on development time and if you do freelance development like myself, being able to easily support multiple platforms/versions quickly is a bonus.

Well, I generally agree with your idea except in this case there’s actually no need of rolling anything of my own because PHP native crypt() function does it all and there’s another native function to generate random string, the functions are well documented. And when I’m using an external library for security there’s also a question of trusting the author that he did everything right. I’m sure the developer of phppass knows what he is doing but first I need to do some research to find out if he is really the expert - I can’t trust blindly anyone, can I? First, I think I can trust the robustness of native PHP functions to an acceptable degree.

PHP native support for hashing passwords has become mature enough to use it directly and it’s extremely simple and cross-platform as of PHP 5.3. I agree with you that if you want to support multiple php versions then the library would make sense. Anyway, both methods will be fine in this case.

I tend to avoid crypt and roll my own custom version of PBKDF2 which does the same thing. I dislike the limit placed on the salt that crypt has in place. While there are solutions around it, I rather just do it myself and not worry about it.

  • I use large random binary data as the salt and pepper. Unique salt per-user and a single global salt (pepper) for the per-site. An attacker would need both.

I wonder - on the one hand you are using a stronger salt and on the other I don’t think you can achieve as many iterations within a given period of time as with a native function - unless you are writing your PBKDF2 in C as a PHP extension. Which is more important? I’m not an expert on security but I’d go for more iterations since the salt length seems to be long enough in crypt() - what benefit will give you a super long salt (apart from being able to add a pepper part to it)? Wikipedia says crypt() is more secure than PBKDF2 against attacks, the PHP documentation about yet unavailable [url=http://php.net/manual/en/function.hash-pbkdf2.php#refsect1-function.hash-pbkdf2-notes]hash_pbkdf2 also recommends crypt() with Blowfish over pbkdf2 for password hashing.

These are the first things I found on the subject but of course I can’t be sure how true they are. Based on this information I’d choose crypt() as the better alternative but I’m willing to learn anything more.

Many thanks for all the info everyone!

I did read about phpass prior to posting this message, however I wanted to stick with native php functions to achieve my task; hence crypt + blowfish. So, thanks for the in-depth response Lemon Juice. Gives me everything that I needed to know.

Regards.

For the applications I work with, performance is more important. That process thousands of log ins every day, resource intensive processes like crypt really brought my servers down, having to pay extra to run even more servers just for handling log ins is bleeding money. So I went with the middle ground, use highly optimized hashing algorithms with high-entropy keys and utilizing minor key-stretching to add to the brute-force protection. Necessary compromise in order to get a responsive application (that serves thousands of users daily) without bleeding money on inefficiency.

However, in reality, PBKDF2 is no more stronger or weaker to crypt depending on how you use it. The only thing crypt is stronger at is when it comes to brute-forcing because it uses an inherit slow, resource-intensive process. But both would result in a computation time greater then what an attacker would be willing to spend on it, even with large cloud computering and GPU acceleration. Thus the end result is the same. (Long keys, make PBKDF2 just as strong as crypt when it comes to brute-forcing without the performance hit.)

hi

What all is required if i want to use blowfish ?

do i need to install some library or script for blowfish ?

vineet

I see, in this case this makes sense!

In PHP 5.3 and later it is built-in, look at the crypt() documentation.