Viewed   344 times

I recently installed PHP 7.3.6 through Plesk's web GUI for a development copy of a web app, as I intend to update our production environment from php 7.0 to 7.3. I decided to take the opportunity to upgrade our password hashing from PBKDF2 to Argon2ID since the PHP core has it already included. I was surprised to get a warning stating that the PASSWORD_ARGON2ID constant is undefined, since I understand it was added in php 7.3.0.

I tried searching for any instance of this error and the only thing I found that was relevant was this undetailed post in a Laravel forum:

The application is hosted on a shared vps with MediaTemple. Centos 7, using nginx as a reverse proxy over Apache. It is a subdomain for development running 7.3.6 along side the main domain which is running the production version of the app, 7.0.33.

$this->password = password_hash('password123', PASSWORD_ARGON2ID, array('time_cost' => 10, 'memory_cost' => '2048k', 'threads' => 6));

I expected the PASSWORD_ARGON2ID constant to be defined but it was reported as undefined:

Use of undefined constant PASSWORD_ARGON2ID - assumed 'PASSWORD_ARGON2ID' (this will throw an Error in a future version of PHP)



This algorithm is only available if PHP has been compiled with Argon2 support. - password_hash

If you want to use it whenever it is available, I would recommend to check with defined or else fallback to a default algorithm.

if(defined('PASSWORD_ARGON2ID')) {
    $hash = password_hash('password123', PASSWORD_ARGON2ID, array('time_cost' => 10, 'memory_cost' => '2048k', 'threads' => 6));
} else {
    $hash = password_hash('password123', PASSWORD_DEFAULT, array('time_cost' => 10, 'memory_cost' => '2048k', 'threads' => 6));
Monday, December 5, 2022


Friday, October 21, 2022

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_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.

Thursday, December 8, 2022

Ok, Let's go through this one by one.

First, it's hashing, not encryption. Encryption is two-way, hashing is one way. We want to hash. We never want to encrypt. Yes, terminology matters. Please use the correct terminology.

Next, each call to password_hash is supposed to return a different hash. That's because it's generating a strong random salt. This is how it was designed, and how you really should be using it.

Further, DO NOT do the "pepper" thing of adding __STR before and after the password. You're doing nothing but potentially weakening the users password (which is not good). If you want more information around why that's a bad idea: Read This Answer.

Continuing, I would highly recommend that you do not use crypt directly. It is actually surprisingly easy to screw up and generate extremely weak hashes. This is why the password_* api was designed. crypt is a low level library, you want to use a high level library in your code. For more information on ways to screw up bcrypt, check out my blog: Seven Ways To Screw Up Bcrypt.

The Password API was designed to be a simple, one-stop shop. If it's not working for you check the following things:

  1. Are you using PHP >= 5.5.0? Or are you using PHP >= 5.3.7 with password_compat?

    1. Is your database column wide enough?

      It needs to be at least 60 characters long.

    2. Are you checking that the result of the function is a string, and not bool(false)?

      If there is an internal error, it will return a non-string from password_hash.

    3. Are you getting any errors?

      Have you turned on error_reporting to its maximum setting (I recommend -1 to catch everything) and checked that the code isn't throwing any errors?

    4. Are you sure you are using it correctly?

      function saveUser($username, $password) {
          $hash = password_hash($password, PASSWORD_BCRYPT);
          // save $username and $hash to db
      function login($username, $password) {
          // fetch $hash from db
          return password_verify($password, $hash);

      Note that each one should be called only once.

  2. Are you using PHP < 5.3.7 with password_compat? If so, this is your problem. You are using the compatability library on an unsupported version of PHP. You may get it to work (certain RedHat distributions have backported the necessary fixes), but you are using an unsupported version. Please upgrade to a reasonable release.

If all else fails, please try running this code and reporting back the output:

$hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
$test = crypt("password", $hash);
$pass = $test == $hash;

echo "Test for functionality of compat library: " . ($pass ? "Pass" : "Fail");
echo "n";

If that returns Fail, you are running an unsupported version of PHP and should upgrade. If it returns pass, than the error is somewhere in your logic (the library is functioning fine).

Tuesday, September 27, 2022

if(!result_query) should be if(!$result_query)

Wednesday, December 14, 2022
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :