php crypt() blowfish salt length backward compatible - php

I used crypt() to hash password, with a blowfish salt like this:
$2a$, 2 digits, $, 21 chars in [a-zA-Z0-9]
Here I made a mistake that chars length after third $ is 21 not 22. But it worked fine so I didn't find the error.
It works on my desktop which running windows and php 5.4.4 and on AWS ec2 which running Amazon linux with php 5.3.x, with that too short salt.
One day I updated AWS php to 5.5.14. then the problem occurred. crypt() return *0 all the time.
After some try, I added a $ at end of the salt so , it become 22 chars. And it works again and return the same hash string as before. Although it doesn't obey the blowfish rule, chars should be [./a-zA-Z0-9]
But now I duplicate this site to another machine which running openSuSE 13.1 with php 5.5.14, This salt failed again.
I downgrade php to 5.4.20 but not help.
The new site still need old database so I have to make that password hash works.
What is the library or module that effect this blowfish salt length error compatibility issue? Tt seems not PHP's version. AWS 5.5.14
Or is there another magic char can save me again? I tried replace th tail $ to each one in [./a-zA-Z0-9] but no lucky, the hash string is different ....

First i would strongly recommend to use the new functions password_hash() and password_verify() to generate and verify new hashes. Of course this doesn't solve your actual problem with the old hashes, but it may be a good idea to mark them as old, so they can be updated the next time the user logs in.
For this old hashes i would try to verify them, generating a salt with a valid last character 22. The crypt function does actually use only part of the bits of character 22 (126 bits of the salt instead of 128). So groups of the last character 22 will end up in the same hash-value.
See the answer to this question Why does crypt/blowfish generate the same hash...
If you try out all relevant characters [.Oeu] as the character 22, the chance is good that one combination will generate the same result as your invalid salt.
EDIT:
Since the used salt becomes part of the password-hash, you should be able to see what was used as character 22 (the 22th character after the third $).

Using (again) '$' as the last character should make your passwords work if you downgrade to PHP 5.4.
This is however, not a long-term solution. Using '$' as the last character has made all your passwords forward-incompatible, because that's not a valid Base64 character (regardless of whether that's regular or bcrypt-compatible Base64).
For as long as you can use PHP 5.4, and that means as long as PHP 5.4 is officially supported, you should re-hash all old passwords whenever they are used.
After PHP 5.4 support is dropped, you'll have no other choice but to just generate new random passwords for your users who've remained with the old hashing scheme and e-mail them.
I must also suggest that you use the password-compat package for your updated passwords. It will give you the password_*() functions that are otherwise available only on PHP 5.5+. The package's author is the same person who implemented the functions in PHP itself, so you can be sure that it is both safe and 100% compatible, providing you with forward-compatibility when you upgrade to 5.5+.

Related

Is PHP's password_hash() Backwards Compatible?

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

crypt() outcome different between PHP versions [duplicate]

I used crypt() to hash password, with a blowfish salt like this:
$2a$, 2 digits, $, 21 chars in [a-zA-Z0-9]
Here I made a mistake that chars length after third $ is 21 not 22. But it worked fine so I didn't find the error.
It works on my desktop which running windows and php 5.4.4 and on AWS ec2 which running Amazon linux with php 5.3.x, with that too short salt.
One day I updated AWS php to 5.5.14. then the problem occurred. crypt() return *0 all the time.
After some try, I added a $ at end of the salt so , it become 22 chars. And it works again and return the same hash string as before. Although it doesn't obey the blowfish rule, chars should be [./a-zA-Z0-9]
But now I duplicate this site to another machine which running openSuSE 13.1 with php 5.5.14, This salt failed again.
I downgrade php to 5.4.20 but not help.
The new site still need old database so I have to make that password hash works.
What is the library or module that effect this blowfish salt length error compatibility issue? Tt seems not PHP's version. AWS 5.5.14
Or is there another magic char can save me again? I tried replace th tail $ to each one in [./a-zA-Z0-9] but no lucky, the hash string is different ....
First i would strongly recommend to use the new functions password_hash() and password_verify() to generate and verify new hashes. Of course this doesn't solve your actual problem with the old hashes, but it may be a good idea to mark them as old, so they can be updated the next time the user logs in.
For this old hashes i would try to verify them, generating a salt with a valid last character 22. The crypt function does actually use only part of the bits of character 22 (126 bits of the salt instead of 128). So groups of the last character 22 will end up in the same hash-value.
See the answer to this question Why does crypt/blowfish generate the same hash...
If you try out all relevant characters [.Oeu] as the character 22, the chance is good that one combination will generate the same result as your invalid salt.
EDIT:
Since the used salt becomes part of the password-hash, you should be able to see what was used as character 22 (the 22th character after the third $).
Using (again) '$' as the last character should make your passwords work if you downgrade to PHP 5.4.
This is however, not a long-term solution. Using '$' as the last character has made all your passwords forward-incompatible, because that's not a valid Base64 character (regardless of whether that's regular or bcrypt-compatible Base64).
For as long as you can use PHP 5.4, and that means as long as PHP 5.4 is officially supported, you should re-hash all old passwords whenever they are used.
After PHP 5.4 support is dropped, you'll have no other choice but to just generate new random passwords for your users who've remained with the old hashing scheme and e-mail them.
I must also suggest that you use the password-compat package for your updated passwords. It will give you the password_*() functions that are otherwise available only on PHP 5.5+. The package's author is the same person who implemented the functions in PHP itself, so you can be sure that it is both safe and 100% compatible, providing you with forward-compatibility when you upgrade to 5.5+.

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.

Do you recognize this password hashing format?

I'm trying to reverse-engineer a password scheme on a legacy PHP application, so I can port the passwords to a new system which will be replacing it. The application has passwords stored in two formats, a newer and an older one. The newer one simply uses crypt() with salt. The older one doesn't seem to have any supporting code any more (at least not in version control), and no hint of what may have been used. I have one account in the old style for which I may know the password, but I don't know how to check it.
The password is stored in the following format:
$1$f1KtBi.v$nWwBN8CP3igfC3Emo0OB8/
It appears to be three fields, delimited by $: 1, f1KtBi.v, and nWwBN8CP3igfC3Emo0OB8/. The first field is always 1. The second and third fields always match the regular expression [a-zA-Z0-9/.]+. The second field always has 8 characters, the third field always has 22.
Have you seen this password storage scheme before? Any idea what hashing mechanism might have been used?
This is the output of the crypt()[docs] function.
The 1 means that it used the MD5 algo internally.
That's most likely produced with crypt(), especially with MD5:
CRYPT_MD5 - MD5 hashing with a twelve character salt starting with $1$
Good luck finding the salt.

Categories