Choosing the method for hashing in PHP

11

My question began when I decided to re-examine the method I was using to obtain the user's password and store it in the database. After reading this , this and this question here on the net, plus many others on other networks, such as this and this ,

We have old yet excellent methods to the present day of generating a hash to store in BD . We also have some, which were considered to be optimal, but which are already falling into disuse (or at least in concept) due to security, as is the case of md5 and sha family.

We also have new methods where, as far as I read, it seems to be the strongest candidate to be used for hash generation, even more recommended than bcrypt or PBKDF2 itself is the case of password_* in PHP >= 5.5 .

Some requirements that I observed to be ideal in crafting hash

    All hash should be created next to a salt ;
  • All salt generated must be unique per client;
  • The function should be slow, to make it harder to attack brute force;

With that in mind, how far can I consider this thinking to be correct and start using only the password_* method? Would it fit into the ideal requirements for hash generation ?

What caught my attention was also the simplicity in terms of the code to use password_* , because with a few lines of code we can get the expected result, for example:

//Gerar um hash
$salt    = $resultadoSalt; //resultado de uma função para obter o salt único
$options = [
    'cost' => 14,
    'salt' => $salt
];
$hash = password_hash($password, PASSWORD_BCRYPT, $options);

//Validar um hash
if (!password_verify($password, $hash)) return false;

In this case, would it be enough to store the result of the password_hash function in the Database? Eg: $2y$14$5e7b5f0ef3cccfac9b902uR4sHJTlTYv3RYt3ApP7PvyTXHmdhN7e or some other process would be recommended?

Remembering that any other considerations on the subject are welcome!

    
asked by anonymous 08.08.2016 / 19:40

2 answers

9
  

With this in mind, how far can I regard this thought as   correct and start using only the password_ * method? He gets   would fit into the ideal requirements for hashing?

The purpose of the PHP password API is to reuse the code. Up to version 7.1 the default algorithm CRYPT_BLOWFISH , but starting with PHP 7.2 we have a new, more secure algorithm called Aragon2 from the libsodium library:

You have already pointed out a basic use of the password_hash API, however it is recommended to leave that the function automatically manages salt for you. In fact, starting with PHP 7, the option to provide a salt

// o cost padrão é 10, aumente caso deseje que o hash seja mais demorado
$options = [
    'cost' => 14,
];

$hash = password_hash($password, PASSWORD_DEFAULT, $options);

Note that I changed to the constant PASSWORD_DEFAULT . From there it will be possible to use a new algorithm without changing its application. From the hash generated PHP will be able to check if a password checks from password_verify :

$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('alguma_senha', $hash)) {
    echo 'Passou!';
} else {
    echo 'Senha incorreta.';
}

When an algorithm is changed, use the password_needs_rehash function to change its hash for a more secure. This function can also be used if cost is increased.

$password = 'alguma_senha';
// $10 é referente ao cost, que nesse caso é 10
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS';

// Um cost mais alto foi utilizado
$options = ['cost' => 11];

// Verifica a senha com o hash
if (password_verify($password, $hash)) {

    // O Hash é o mais recente com base nas configs passadas?
    // Imagine que o PASSWORD_DEFAULT foi alterado...
    if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {

        // Se sim, crie um novo hash dessa senha
        $newHash = password_hash($password, PASSWORD_DEFAULT, $options);

        // Passos para atualizar o hash no banco
    }

    // Usuário logado
}
  

Some requirements that I observed as ideal in hashing   would be:

     
  • All generated hash must be created next to a salt;
  •   
  • All generated salt must be unique per client;
  •   
  • The function should be slow, to make it harder to attack brute force;
  •   

Looking at the requirements, items 1 and 2 already have a salt randomly generated by password_hash if nothing is sent in options .

The third item needs to be evaluated by finding the relationship between a cost that takes time to break with brute force and the waiting time of a common user in the application. The longer the cost , the longer it will take to break it, but do not make your user wait 1 minute to try to log in:)

In a brute-force attack where the attacker got the hashes from the database, what will delay his work is the defined cost .

But in the case of a brute-force attack from an external endpoint of your site, you can apply some throttling technique, slow for some requests only without affecting the user experience. This is done by inserting breaks in script execution for a few seconds for incorrect login attempts, making the requests take longer.

Another alternative is to temporarily disable the user and only activate from another contact medium (contact service desk , sms or phone verification, password reset by email, for example).

In short, when working with passwords in PHP use password_* to:

  • Simplify your code
  • Do not have problems in future migrations
  • Possibility to change the cryptographic engine in the future transparently for the application
16.08.2016 / 04:34
1

I would recommend reading this article: link

I find it interesting to use bcrypt with high cost. You even have an interesting code in the php documentation to find an acceptable cost:

/**
 * This code will benchmark your server to determine how high of a cost you can
 * afford. You want to set the highest cost that you can without slowing down
 * you server too much. 8-10 is a good baseline, and more is good if your servers
 * are fast enough. The code below aims for ≤ 50 milliseconds stretching time,
 * which is a good baseline for systems handling interactive logins.
 */
$timeTarget = 0.05; // 50 milliseconds 

$cost = 8;
do {
    $cost++;
    $start = microtime(true);
    password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
    $end = microtime(true);
} while (($end - $start) < $timeTarget);

echo "Appropriate Cost Found: " . $cost . "\n";
    
08.08.2016 / 21:19