28

So I heard that PHP 7.2 introduced the new Argon2 algorithm. But I'm confused on how I can use it with my existing code. For instance, I have this

$password = password_hash('somepassword', PASSWORD_DEFAULT, ['cost' => 12]);

Does PASSWORD_DEFAULT now use Argon2? What, if anything, do I need to change with password_verify? Is bcrypt considered insecure now?

Machavity
  • 28,730
  • 25
  • 78
  • 91

2 Answers2

59

What is Argon2? Is bcrypt bad now?

Prior to PHP 7.2, the only hashing algorithm password_hash used was bcrypt. As of this writing, bcrypt is still considered a strong hash, especially compared to its predecessors, md5 and sha1 (both of which are insecure because they are fast). Argon2 is simply a costlier algorithm to brute force

Argon2i uses data-independent memory access. It is slower because it makes more passes over the memory to protect from trade off attacks. It is highly recommended for password hashing and password-based key derivation.

Bcrypt is still an acceptable hash for passwords. There's no need to switch if you don't want to (as of the 7.2.0 release). Also, PASSWORD_DEFAULT should only change (per PHP Internals policy) on the next full release (7.3.0 or higher). If you want to ensure you continue with only bcrypt, you can use PASSWORD_BCRYPT instead. This is unnecessary, however, as we'll discuss below.

How do you use Argon2?

First, we'll switch the second argument of password_hash over to one of these to constants

  • PASSWORD_ARGON2I - PHP 7.2.0+
  • PASSWORD_ARGON2ID - PHP 7.3.0+ (preferred if available, see notes below)

and then we'll need to change our options. bcrypt uses cost as the parameter for how many times it iterates over the password (higher cost = longer hashing time). There's different cost factors, however

password_hash('somepassword', PASSWORD_ARGON2I, ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]);

From the manual we see what these options do

  • memory_cost - Maximum memory (in bytes) that may be used to compute the Argon2 hash (default 1024)
  • time_cost - Maximum amount of time it may take to compute the Argon2 hash (default 2)
  • threads - Number of threads to use for computing the Argon2 hash (default 2)

Understand, before you go changing these, that a higher cost here will slow down your script. You'll want to run a test on your server to find a setting that works best for you. This is typically by looping over several iterations of a given cost. The PHP manual gives an example of this if you need one.

Also note that, while bcrypt stores 60 characters, Argon2 can require more than that. You should, ideally, make your password field store 255 characters.

What do we change in password_verify?

The answer here is... nothing. Understand that password_verify is smart enough to figure out what algorithm was used and handle it appropriately. As mentioned above, this means that if you are using PASSWORD_DEFAULT, the default can change and not negatively affect you (although you may need to adjust the cost parameters). password_verify simply requires an algorithm it supports. If you switch from bcrypt to Argon2, both will verify the same way, as all the necessary data (salt, hash and cost) are stored for you.

//Works for both bcrypt and Argon2
if(password_verify($user_password, $stored_hash)) {
    // password validated
}

If you want to upgrade the hashes from bcrypt, you can do this when a user successfully logs in (and thus supplied you with the un-hashed password). Simply check if your hash starts with $2y$ (the bcrypt marker). If it does, pass the supplied password to password_hash again, but with the Argon2 arguments, and save it to the password field of the logged-in user.

What is Argon2ID?

Introduced in PHP 7.3, Argon2ID makes some improvements over Argon2I as noted in this Crypto.SE question

The best tradeoff attack on 1-pass Argon2id is the combined low-storage attack (for the first half of the memory) and the ranking attack (for the second half), which bring together the factor of about 2.1.

Argon2ID works with the same arguments that Argon2I works with.

Community
  • 1
  • 1
Machavity
  • 28,730
  • 25
  • 78
  • 91
  • It would be useful on this answer to add how to load Argon2 into the PHP runtime modules. You answer implies PHP 7.2 has it by default whereas I can only show on my settings that PHP 7.1 *doesn't*, Cheers – Martin Jan 03 '18 at 14:34
  • 1
    @Martin There's no module to load. If you're running 7.2.0 or later it's there (`password_hash` is [core PHP](http://php.net/manual/en/password.installation.php)). I am unaware of any backporting options, if that's what you mean. – Machavity Jan 03 '18 at 15:02
  • 3
    Instead of checking what the string starts with, you should probably use http://php.net/manual/en/function.password-needs-rehash.php as *"This function checks to see if the supplied hash implements the algorithm and options provided"*. – jeroen Sep 13 '18 at 11:26
  • _that a higher cost here will **slow down your script.**_ Does this happens only during the process of `password_hash()` OR `password_verify()` as well? – Irfandy Jip Dec 19 '18 at 09:03
  • 1
    @IrfandyJip Both. Remember, `password_verify` takes all the options of the hash and the password to be verified and **generates a new hash**. If the new hash (with the same exact options) matches the old, the password is verified. – Machavity Dec 19 '18 at 13:16
  • Is it the `memory_cost` and `time_cost` a must have? Or we can just simply do password_hash(`"somepassword" , PASSWORD_ARGON2ID)`? – SilverSurfer Jul 12 '19 at 10:28
  • 1
    @SilverSurfer Costs are not required. If you don't supply values, PHP uses the default – Machavity Jul 12 '19 at 11:13
-1

Only if you use PHP 7.3: I've created 2 simple and slim functions to use Argon2ID with PHP:

function argon2idHash($plaintext, $password, $encoding = null) {
    $plaintextsecured = hash_hmac("sha256", $plaintext, $password);
    return $encoding == "hex" ? bin2hex(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : ($encoding == "base64" ? base64_encode(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : password_hash($plaintextsecured, PASSWORD_ARGON2ID));
}

function argon2idHashVerify($plaintext, $password, $hash, $encoding = null) {
    $plaintextsecured = hash_hmac("sha256", $plaintext, $password);
    return password_verify($plaintextsecured, $encoding == "hex" ? hex2bin($hash) : ($encoding == "base64" ? base64_decode($hash) : $hash)) ? true : false;
}

To get an hashed value use (the last parameter is optional, you can choose hex, base64 or nothing) [return => string]:

$salt = "LALALA";
argon2idHash($clearvalue, $salt, "hex"); // with encoding
argon2idHash($clearvalue, $salt); // without encoding

To verify an hashed value use (parameter $salt must match the salt set at the time of hashing also the same rule applies to encoding if used) [return => bool]:

$salt = "LALALA";
argon2idHashVerify($clearvalue, $salt, $hashtoverify, "hex") ? "match" : "dont match"; // with encoding
argon2idHashVerify($clearvalue, $salt, $hashtoverify) ? "match" : "dont match"; // without encoding

Finally, if you know PHP you can modify these functions to your liking, but for now it's the best way I know to securely store passwords in databases.

Marco Concas
  • 772
  • 8
  • 18
  • The function that I created as the only thing transforms the text to be encrypted in sha256-hmac with a password or salt chosen by you, then the standard argon function is used. Mine is an extra step that gives additional security. You now say that argon2 is safe, we will have to see in a few years how much it will be. For the downvote your motivation I do not consider it valid. – Marco Concas Mar 13 '21 at 14:16