Is PHP's password_hash() Backwards Compatible?

1.3k views Asked by At

Specifically, when using the PASSWORD_DEFAULT algorithm, as per the php page

"Note that this constant is designed to change over time as new and stronger algorithms are added to PHP. For that reason, the length of the result from using this identifier can change over time. Therefore, it is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice)."

and

"Note: Updates to supported algorithms by this function (or changes to the default one) must follow the following rules: Any new algorithm must be in core for at least 1 full release of PHP prior to becoming default. So if, for example, a new algorithm is added in 7.5.5, it would not be eligible for default until 7.7 (since 7.6 would be the first full release). But if a different algorithm was added in 7.6.0, it would also be eligible for default at 7.7.0. The default should only change in a full release (7.3.0, 8.0.0, etc) and not in a revision release. The only exception to this is in an emergency when a critical security flaw is found in the current default."

So, when upgrading PHP to a version that uses a different algorithm for PASSWORD_DEFAULT, does that prevent users from logging in if their passwords were hashed with the old algorithm?

2

There are 2 answers

1
Adam Lavin On BEST ANSWER

The short answer is yes, it is backwards compatible, providing your storage allows for longer password hash lengths.

When you generate a password you will get it in a specific format.

I've generated two passwords using bcrypt and the newer argon2i hash. (Coming in PHP 7.2 with the introduction of libsodium)

FYI: My code here is based upon the libsodium extension since I haven't got php 7.2 downloaded, and wasn't going to install sodium_compat just for the aliases. The php 7.2 syntax will not contain any namespaces.

$bcrypt = password_hash('insecurepassword', PASSWORD_BCRYPT);
$argon2i = \Sodium\crypto_pwhash_str('insecurepassword', \Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, \Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);

var_dump($bcrypt, $argon2i);

The output I got when I ran that was this

string(60) "$2y$10$jiT0NF3u426kguHes8ZBputRE/n9OSdPi5HhHvEWW4mX1XDwKwy1e"
string(96) "$argon2i$v=19$m=32768,t=4,p=1$Ho4Vzgp5nzQkLlp99P+ViA$bDqX8UUlSnfLRCfFBzBnFhWr/hzHzuuUCfZ0LSIns64"

Both passwords are in the same format more or less. If you explode the $ you end up with each piece of the puzzle required to verify the password.

The first section contains the algorithm. 2y for bcrypt, argon2i for... argon2i. (surprise!)

The next bits contain the configuration options, so in bcrypt's case the cost. In argon2i's case, there's some extra configuration.

The final section contains the actual password and salt for the algorithm to check against.

For a bit more of a visual breakdown checkout the php docs.

Even though I've used some different functions to demo some output, there is an rfc that was accepted that introduced support for the argon2i hash to the password_hash functions in 7.2.

So when that time comes, password_verify will "just work" regardless of if you give it a bcrypt or argon2i hash to verify a password against.

0
Vural On

Is a one-way hashing algorithm, you can't decrypt hashes.

Use password_verify to check, if the password matches the stored hash:

<?php
  $hash = 'your-hash';

  if (password_verify('pass', $hash)) {
    echo 'Password is valid';
  } else {
    echo 'Password is not valid!'; 
  }