Choice of hash generation method in PHP

Asked

Viewed 1,508 times

12

My doubt started when I decided to re-examine the method I was using to get the user’s password and store it in the database. After reading that one, that one and that one questions here on the network, in addition to many others on other networks, such as that one and that one, some doubts arose me.

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

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


Some requirements I have observed as ideal in the elaboration of hash would be:

  • All hash generated must be created next to a salt;
  • All salt generated must be unique per customer;
  • The function should be slow, to hinder the attack of brute force;

With that in mind, as far as I can consider this thought to be correct and start using only the method password_*? It would fit the ideal requirements for generating hash?

What struck me was also the simplicity in terms of code to use password_*, because with few lines of code we can get the expected result, 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, store the function result password_hash in the Database would be enough? Ex.: $2y$14$5e7b5f0ef3cccfac9b902uR4sHJTlTYv3RYt3ApP7PvyTXHmdhN7e or some other process would be recommended?

Remembering that any other considerations on the subject are welcome!

  • 1

    In fact, in PHP 7 it is considered obsolete to inform the salt, this is automatically generated by the function. So, I believe that inform the salt is not the best option. The criteria you observed is correct, just add "one": usage time and popularity. bcrypt is old (before 2000). There is scrypt, unless mistaken, it was published in 2009. Precisely because it is recent it is not yet "so accepted", it is necessary that it resists for more time alive. But in answer to the question, I believe it would be enough. I think the function was created so, easy, to see if they stop using MD5. :P

  • @Inkeliz The problem of using the salt generated by the function is precisely this, being random, that is, when the client is logged in, salt will not be the same generated when he created the account, so the hash would be different, right? At least from the test I took and what I read, that was the conclusion.

  • 1

    When to use the password_verify will not compare one hash to another, nor should you insert salt. So salt should be random, even to be unique. The PHP manual does not recommend using a custom salt or re-invent the wheel to generate salt, "It is strongly Recommended that you do not generate your Own salt for this Function.". Try removing the salt and it will work the same way, even if it generates a hash that is totally different from each other. How it works I’m waiting for someone to answer it too. : D

  • @Inkeliz, I’m gonna run some tests that way, so I don’t remember those details. What confused me was just this phrase "don’t re-invent the wheel, there are already many options available". But there are so many, and so many that say they are good, that I don’t really know what the good is! hahaha

  • 1

    Just to answer the question of salt. The salt is already stored in the password itself, or a chunk of it. For example: if the salt for of 1234567891234567891234, the password of abc will be $2y$14$123456789123456789123uqefxzc/iUTZnhJmbDgxEKiWGTixIZu6. Note that the second $ indicates the cost (14), then just salt: being visibly 123456789123456789123, only lacked the 4, the last number, which then I know where it went (but change the last number of the salt does not change the hash, so...). Anyway, salt is already embedded in the hash, so it can perform the comparison.

  • @Inkeliz Yes, this logic I had even understood. However, as salt does not appear fully, I imagined that there was some other however.. But now I have been able to look at this question differently. And I assume that this has no impact on the security issue, correct?

  • Why don’t you draw the line at every hour? Thus avoids brute force enough, if the hacker exceeds this limit will have to wait "years", to get back to.

  • 2

    @Gonçalo this is a good complement to the application, but it doesn’t solve the hash problem, see the other posts linked in the question itself. The salt and hash are not only to prevent access to the application, but to prevent access to credentials even if the attacker gets a copy of the DB passwords.

  • 1

    In fact the current question seems more like a request for confirmation of understanding, or even a debate about what has already been posted, than a new doubt. When so, I recommend leaving comments in the original posts, which we can try to complement, explain, improve etc.

  • @Bacchus I understood his answer more as a theoretical and explanatory basis between the methods and characteristics of each. While my question is more about use/security coupled with the simplicity of code thatpassword_* has been offering when compared to other methods, such as md5 (generating a salt "manual"), the very PBKDF2 or other techniques.

  • Do not understand as a theoretical basis only, it is to use in practice even :) But do not just stick to my answer, which is an adaptation of an excellent existing post of the OS in English. There’s a lot of other important information in other answers there, which are pretty interesting. I suggest rereading trying to understand what problems each technique tries to solve, which is easier to see what are the problems of a md5 (collision and speed, for example). But if you can describe more accurately the parts you don’t understand, as I said, we can try to help.

  • As for the PHP password, for you to know if it is "good enough", it is a matter of understanding the past concepts. I’d say for normal use, with bcrypt, it’s enough for most uses. But only you can really know the value of the information being protected and the risks involved. Security does not have "ready solution" for all cases. Just understanding, and updating. It will always be a race between defender and attacker, balanced by the value of information. Remembering that this value may include a third party password that should not, but can be reused.

  • @Bacco yes yes, I fully understand your point of view. Maybe this "vision" that you are trying to give me is not so clear yet because of my lack of experience (because I am more of front end - design), but I will reconsider these last topics that you pointed out and reread more calmly! = D

Show 8 more comments

2 answers

9


With that in mind, as far as I can consider this thought as correct and start using only the password_*? method if fit the ideal requirements for hashing?

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

You’ve already pointed out a basic use of the password_hash, but it is recommended to let the function automatically generate the salt for you. Including, from PHP 7 the option to provide a salt custom was discontinued because it allowed developers to use Salts unsafe.

// 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 the second argument to the constant PASSWORD_DEFAULT. From this it will be possible to use a new algorithm without changing its application. From the hash generated PHP will be able to verify if a password checks from the password_verify:

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

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

When an algorithm change occurs, use the function password_needs_rehash to change your hash to a more secure one. This function can also be used if the cost be 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 I have observed as ideal in hashing would be:

  • Every generated hash must be created together with a salt;
  • All salt generated must be unique per customer;
  • The function should be slow, to hinder the attack of brute force;

Observing the requirements, items 1 and 2 are already having a salt generated at random by password_hash if nothing is sent in options.

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

In a brute force attack where the attacker has obtained the hashes from the bank, what will delay his work is the cost defined.

But in the event of a brute force attack from a endpoint external of your site, you can apply some technique of throttling, which consists in slowing down this processing for some requests only without affecting the user experience. This is done by inserting breaks in script execution for a period of a few seconds for incorrect login attempts, causing requests to take longer.

Alternatively, disable the user temporarily and only activate from another contact medium (contact with service desk, confirmation by text or phone, reset password by email, for example).

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

  • Simplify your code
  • No problems in future migrations
  • Possibility to change the encryption engine in the future in a transparent way for the application
  • In the generation of hash, where I demonstrated with the use of PASSWORD_BCRYPT and you changed it to PASSWORD_DEFAULT what would be the main reason? Or what better option? If I use bcrypt I can’t use the password_needs_rehash in the future?

  • The idea of password_hash is to enable vc to change the algorithm in a simple way. PASSWORD_DEFAULT and the PWORD_BCRYPT today are the same thing, but if in PHP 8, for example the PASSWORD_DEFAULT change to PASSWORD_BATATA, you’ll still be using the PASSWORD_BCRYPT and will need to change your code to use PASSWORD_BATATA

  • 1

    @Celsomtrindade in other words, the idea of PASSWORD_DEFAULT is to always be the "best you have at the moment". If you change PHP and come better, your application will automatically improve the hash of each user who logs in.

  • @Bacco in this case, only the new users who create account, right? And then for the old ones, only use password_needs_rehash it takes care to update everything automatically. Correct?

  • @Celsomtrindade will not update your bank alone, it will only return a Boolean and tell you whether you need to upgrade or not. See in the answer that shows the use of password_needs_rehash has explained the flow in comments.

  • @gmsantos yes yes, I ended up expressing myself badly. What I meant was just this, of it, automatically (without having to do any manual comparison), return me a value indicating the need to do a password update.

  • The password_needs_rehash can’t do that because he doesn’t know what the original password was used to generate that hash. So this step has to be done when logging in, because at this point you have already compared the user input with the hash and know that it is valid.

  • Any more @Celsomtrindade questions? remembering that the goal of the answer was to give a north to how to use the functions of password_*. If you have other punctual questions you can take it off in a new question :)

Show 3 more comments

1

I would recommend reading this article: https://crackstation.net/hashing-security.htm#phpsourcecode

I find it interesting to use bcrypt with high cost. It even has an interesting code in 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";
  • As stated above, as to the use I am already using the BCRYPT, to the standard password_* php itself offers. But doubt is the comparison of this with other methods, such as the techniques of md5 and salt, PBKDF2, and so on..

Browser other questions tagged

You are not signed in. Login or sign up in order to post.