Is PHP's password_hash() Backwards Compatible? - php

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?

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.

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!';
}

Related

Using password_hash PASSWORD_DEFAULT legacy concerns

From the php page: http://php.net/manual/en/function.password-hash.php
PASSWORD_DEFAULT - Use the bcrypt algorithm (default as of PHP 5.5.0). 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).
Would this mean that if I password_verify a password with a later version e.g. PHP8, it might not be able to decipher the stored password correctly? Or is all that information safely embedded already and all I have to do is store the output of password_hash into my DB and just call password_verify for validation without worries?
Would this mean that if I password_verify a password with a later version e.g. PHP8, it might not be able to decipher the stored password correctly?
No. The password hash includes information on which specific algorithm was actually used. If a future version of PHP supports more than one password hashing algorithm, it will be able to read that information from an old hash to figure out how to reproduce it.
(Specifically, the $2y$ prefix currently present on all hashes indicates that they were generated using the PASSWORD_BCRYPT algorithm. Any future algorithm will use a different prefix.)
Yes, all the information is safely embedded, provided that your database doesn't truncate the encrypted password. Which is pretty much a given, I'd think.
What that warning is driving home is that, for your future self's sanity, you store the information in a field whose length is flexible. Historically, like with MD5, one might have chosen CHAR(32). But with bcrypt, you need to choose something more flexible. That might be:
VARCHAR(255)
CHAR(60), with this column being the last one in the table, so that extending its length might not require the RDBMS to reshuffle table.

What will happen if they changed PASSWORD_DEFAULT in PHP Password library?

Consider this line of code by using PHP:
$password = password_hash($password, PASSWORD_DEFAULT);
What will happen if they changed the default password hashing algorithm? I mean I will be having hashed password inside the database. Then, from my own understanding, it will be impossible to check the password because the hashing algorithm will be totally changed.
What would happen is that newly-hashed passwords will be using the new algorithm - obviously.
However, you shouldn't be concerned about this, because the whole thing is designed with forward-compatibility in mind - your code won't be broken when the default algorithm changes, as long as you're using the password_*() functions correctly.
By correctly, I mean use password_verify().
password_verify() accepts a plain-text password and a hash, and it can easily determine what the used algorithm is by looking at the hash that you feed it. Therefore, it would also still be able to verify a password that has been hashed using an old algorithm - not just the previous one, but any algorithm that is supported.
In fact, the sole purpose of the PASSWORD_DEFAULT constant is that you can easily migrate older hashes to a new algorithm (once one is added). This happens the following way:
When a user logs in, you verify their password via password_verify() (any hashing algorithm that has a PASSWORD_<name> constant will work).
You call password_needs_rehash(), and if the password you just verified is using an older algorithm (or a lower 'cost' paramater) - it will return boolean TRUE.
If boolean TRUE was indeed returned, you can now replace the old hash with one that uses the new algorithm; you can do that during a login, because the user just gave you the password and you verified that it is correct.
In summary - it's a really, really well-designed API and it solves problems for you that you haven't even thought about. Don't worry about it.
Edit (noted in the comments):
It should be noted, however, that new algorithms will quite probly result in longer hash lengths, so if you're storing the passwords in a database - don't limit the field's length (i.e. use a varchar(255) field).
Just for clarification, I would like to add to the answer that PHP uses the following structure. Thus, the password_needs_rehash() and password_verify() functions will check the algorithm and cost and do their work to keep everything compatible and correct.
Source: http://php.net/manual/en/faq.passwords.php

What is the default algorithm in password_hash

Reading the documentation about a new password_hash function for PHP 5.5, I am wondering, what is the default algorithm:
password_hash("rasmuslerdorf", PASSWORD_DEFAULT);
Documentation about it does not clarify this: http://www.php.net/manual/en/password.constants.php
I have had a look into the PHP source code. It defaults to bcrypt in PHP5.5.
From ext/standard/php_password.h line 31:
#define PHP_PASSWORD_DEFAULT PHP_PASSWORD_BCRYPT
This has been updated in the documentation at password_hash() and will be updating shortly in the constants page (I just committed the documentation change about an hour or so ago).
This will be live today at password.constants
From the updated constants page (which hasn't gone live yet, but will be later today):
Available algorithms:
PASSWORD_BCRYPT (integer)
PASSWORD_BCRYPT is used to create new password hashes using the CRYPT_BLOWFISH algorithm.
This will always result in a hash using the "$2y$" crypt format, which is always 60 characters wide.
Supported Options:
salt - to manually provide a salt to use when hashing the password. Note that this will override and prevent a salt from being automatically generated.
If omitted, a random salt will be generated by password_hash() for each password hashed. This is the intended mode of operation.
cost - which denotes the algorithmic cost that should be used. Examples of these values can be found on the crypt() page.
If ommitted, a default value of 10 will be used. This is a good baseline cost, but you may want to consider increasing it depending on your hardware.
PASSWORD_DEFAULT (integer)
The default algorithm to use for hashing if no algorithm is provided. This may change in newer PHP releases when newer, stronger hashing algorithms are supported.
It is worth noting that over time this constant can (and likely will) change. Therefore you should be aware that the length of the resulting hash can change. Therefore, if you use PASSWORD_DEFAULT you should store the resulting hash in a way that can store more than 60 characters (255 is the recomended width).
Values for this constant:
PHP 5.5.0 - PASSWORD_BCRYPT
As far as when and how PASSWORD_DEFAULT will be updated, that's on the password_hash() documentation page:
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 5.5.5, it would not be eligible for default until 5.7 (since 5.6 would be the first full release). But if a different algorithm was added in 5.6.0, it would also be eligible for default at 5.7.0.
The default should only change on a full release (5.6.0, 6.0.0, etc) and not on a revision release. The only exception to this is in an emergency when a critical security flaw is found in the current default.
The documentation is actually rather specific, if a bit poorly worded; the hash is the strongest one PHP believes it has available at the time, and is subject to change at any time. The hashes produced by password_hash contain a bit of data at the start that indicates which has was initially used to produce them, allowing such upgrades to occur automatically as new hash algorithms become available, without breaking any hashes you have already stored in a database.
Since bcrypt is the only algorithm currently defined, you can probably assume it's the default, but a quick way to verify would be to make a simple PHP script that hashes the same string twice, once with each option, and with a fixed salt, and prints the resulting hashes; they will probably match.
The original password_hash spec may also be of some help. https://wiki.php.net/rfc/password_hash

Where 2x prefix are used in BCrypt?

The question is the same title, Where $2x$ is used in BCrypt?
The following scenario is right?
We have a set of passwords that hashed with $2a$ prefix already, when the Server PHP version was earlier 5.3.7. Now we upgraded the PHP to 5.3.7+, now we must firstly verify previous passwords with $2x$ algorithm then rehash the password with $2y$ prefix. That's right?
BCrypt variants
$2$
BCrypt was designed by the OpenBSD people. It was designed to hash passwords for storage in the OpenBSD password file. Hashed passwords are stored with a prefix to identify the algorithm used. BCrypt got the prefix $2$.
This is in contrast to the other algorithm prefixes:
$1$: MD5
$3$: NTHASH
$5$: SHA-256
$6$: SHA-512
$7$: scrypt
$2a$
The original BCrypt specification did not define how to handle non-ASCII characters, or how to handle a null terminator. The specification was revised to specify that when hashing strings:
the string must be UTF-8 encoded
the null terminator must be included
$2x$, $2y$ (June 2011)
A bug was discovered in crypt_blowfish🕗, a PHP implementation of BCrypt. It was mis-handling characters with the 8th bit set.
They suggested that system administrators update their existing password database, replacing $2a$ with $2x$, to indicate that those hashes are bad (and need to use the old broken algorithm). They also suggested the idea of having crypt_blowfish emit $2y$ for hashes generated by the fixed algorithm. Nobody else, including canonical OpenBSD, adopted the idea of 2x/2y. This version marker was was limited to crypt_blowfish🕗.
The versions $2x$ and $2y$ are not "better" or "stronger" than $2a$. They are remnants of one particular buggy implementation of BCrypt.
$2b$ (February 2014)
A bug was discovered in the OpenBSD implementation of BCrypt. They were storing the length of their strings in an unsigned char. If a password was longer than 255 characters, it would overflow and wrap at 255.
BCrypt was created for OpenBSD. When they have a bug in their library, they decided its ok to bump the version. This means that everyone else needs to follow suit if you want to remain current to "their" specification.
http://undeadly.org/cgi?action=article&sid=20140224132743 🕗
http://marc.info/?l=openbsd-misc&m=139320023202696 🕗
The version $2b$ is not "better" or "stronger" than $2a$. It is a remnant of one particular buggy implementation of BCrypt. But since BCrypt canonically belongs to OpenBSD, they get to change the version marker to whatever they want.
There is no difference between 2a, 2x, 2y, and 2b. If you wrote your implementation correctly, they all output the same result.
And if you were doing the right thing from the beginning (storing strings in utf8 and also hashing the null terminator) then: there is no difference between 2, 2a, 2x, 2y, and 2b. If you wrote your implementation correctly, they all output the same result.
The only people who need to care about 2x and 2y are those you may have been using crypt_blowfish. And the only people who need to care about 2b are those who may have been running OpenBSD.
All other correct implementations are identical and correct.

Alternative to bcrypt when saving passwords in PHP 5.2

I'm using bcrypt locally since xampp has PHP 5.3 but online my hosting account only has PHP 5.2. Is there a good alternative I can use which works for 5.2?
I think i should update and improve this answer, because i learned a lot about password hashing in the last years.
PHP version 5.5 will provide a convenient way to use BCrypt, for PHP version 5.3.7 and above there exist a compatibility pack. Please have a look at this answer.
For PHP versions before 5.3 it is recommended to use the phpass library, they support PHP back to version 3.
I'm using bcrypt ... Is there a good alternative I can use which works for 5.2?
See Openwall's PHP password hashing framework (PHPass). Its portable and hardened against a number of common attacks on user passwords. The guy who wrote the framework (SolarDesigner) is the same guy who wrote John The Ripper and sits as a judge in the Password Hashing Competition. So he knows a thing or two about attacks on passwords.
Check out the Mcrypt PHP extension. It's been around for a long time and has several different algorithms. bcrypt appears to just be a Blowfish wrapper. You could just as easily use PHP's crypt() function, and pass the appropriate salt to force the function to use Blowfish:
// crypt($plaintext, $salt);
// How you define $salt determines the encryption algorithm used
$hash = crypt('PASSWORD', '$2a$12$Some22CharacterSaltXXO');
echo $hash;
// Output would be $2a$12$Some22CharacterSaltXXO6NC3ydPIrirIzk1NdnTz0L/aCaHnlBa
The PHP manual page (linked above) has the explanation of why the password salt looks the way it does in my example above. The $2a$ tells PHP to use Blowfish, the 12$ is a cost modifier; a number between 04 (yes, it has to be 2 digits) and 31 that (I believe) effects the number of iterations the hashing mechanism uses. As you can see, the salt is included in the output from the call to crypt(), so when you need to check something against the hash you need to retrieve the hash first (from the file or database where it's stored) to pull out the salt.
It depends on where and what you store your passwords for.
For a online website (with users etc+) i would done this:
$hash = "jr38028(/#Fjg4i4g438h9)(#Hhhf3,..;uh#F)8";
$hashed = sha1($hash . $PASSWORD . $hash); // where $PASSWORD is the variable thats holding the password.
echo $hashed; // shows the hashed password.
Edited after doing something wrong. Forgot to hash inside function, also changed to sha1 instead of md5. And haters, love you too <3

Categories