Zend PHP 7 Certification – Security – Password Hashing API

This post covers the Password Hashing section of the PHP Security chapter when studying for the Zend PHP 7 Certification.

It is common knowledge that you should never store passwords in plain text. This leads onto discussions about what is the best form of password hashing making them difficult to crack.

PHP hashing algorithms such as md5() and sha1() are very fast and efficient, however with modern computers, it has become trivial to brute force the output of these algorithms to determine the original input.

Because of how quickly a modern computer can “reverse” these hashing algorithms, it is recommended to not use these hashing algorithms. Recommended functions include crypt() and password_hash(), a password hashing API introduced in PHP 5.5.

crypt() will return a hashed string using the standard Unix DES-based algorithm or alternative algorithms that may be available on the system.

string crypt ( string $str [, string $salt ] )

The salt parameter is optional, however you should specify one as crypt() creates a weak password without the salt. Furthermore, in PHP 5.6 or later, a E_NOTICE is raised without it.

You can then use hash_equals() to verify the hashed password.

<?php
$hashed_password = crypt('mypassword', $salt);
if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) {
   echo "Password verified!";
}

password_hash() creates a new password hash using a strong one-way hashing algorithm.

$password = "dY^543U%?%$";
$password = password_hash($password, PASSWORD_DEFAULT);

The second parameter passed in the function is the algorithm. The following algorithms are supported

  • PASSWORD_DEFAULT – Uses the bcrypt algorithm to create the hash.
  • PASSWORD_BCRYPT – Uses the CRYPT_BLOWFISH algorithm to create the hash. This will produce a standard crypt() compatible and the result will always be a 60 character string, or FALSE on failure.

The options supported for the PASSWORD_BCRYPT algorithm are:

  • salt – The salt used manually will override the default salt that gets generated
  • cost – Denotes the algorithmic cost that should be used. If omitted, the default value of 10 is used

An example of manually setting the salt and the cost can be seen below:

$options = [
    'cost' => 11,
    'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM),
];
echo password_hash("somepassword", PASSWORD_BCRYPT, $options);

The salt option has been deprecated as of PHP 7.0. It is now preferred to simply use the salt that is generated by default.

You can verify the hash using the password_verify() function.

$hash = password_hash("somepassword", PASSWORD_DEFAULT);
if (password_verify('somepassword', $hash)) {
    echo 'Password is valid.';
} else {
    echo 'Invalid password.';
}

You can retrieve the details of the password hash using the password_get_info() function.

$password = "somepassword";
$hash = password_hash($password, PASSWORD_DEFAULT);

print_r(password_get_info($hash));
// Prints: Array ( [algo] => 1 [algoName] => bcrypt [options] => Array ( [cost] => 10 ) )

The password_needs_rehash() function checks to see if the supplied hash implements the algorithm and options provided. If not, it is assumed that the hash needs to be rehashed.

The algorithm used by PASSWORD_DEFAULT is subject to change as different versions of PHP are released, so this function would be used here.

$password = 'somepassword';
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS';

// The cost parameter can change over time as hardware improves
$options = array('cost' => 11);

// Verify stored hash against plain-text password
if (password_verify($password, $hash)) {
    // Check if a newer hashing algorithm is available
    // or the cost has changed
    if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
        // If so, create a new hash, and replace the old one
        $newHash = password_hash($password, PASSWORD_DEFAULT, $options);
    }

    // Log user in
}

View the other sections:

Note: This article is based on PHP version 7.0.