phppasswordscrypt

why does php crypt function with hash return hash


I recently inherited a legacy php project that rolled it's own auth using the crypt function. I'm working on reverse engineering it but don't quite understand how this works.

the code to save this password looks like the following

$pass = crypt('password') // no salt. This result is saved to password col of user table

To verify the password hash, the app first pulls the hash from the DB then verifies it against the hash something like this


$pass = get_submitted_password()

$hash = get_the_hash_from_database()

return $hash == crypt($pass, $hash)   // the hash is used as the salt

The app is 5.* but i've verified that this snippet of code still works on php 8.1. Can someone explain how/why this works?


Solution

  • The strategy you describe is actually what is demonstrated in the first example on the official documentation of the crypt() function.

    As I understand it:

    With only one parameter the crypt() function hashes a password. For that "some" algorithm is used. Which one depends on what algorithms are available and on other settings, reason is that different systems support different sets of algorithms.

    To be able to compare hashes created in the past with current hashes (the set of available algorithms might have changed over time...) the function has a signature that accepts a second argument, called "salt". That can be a typical "salt string" as it is called in cryptography, but usually it is indeed a previously created hash.

    What the crypt() function does is this: it parses the structure of the string provided as "salt". If that "looks like" a hash created by the crypt function itself, then the salt used for the prior creation can be extracted from the hash string. Take a look at a few examples, you will see something like "$2a$07$usesomesillystringforsalt$" for example in case a Blowfish algorithm has been used. The "2a$07" in there is the salt the algorithm used to create that hash string. The salt is embedded in the final hash for exactly that purpose: so that it can be reused for verifying the hash at a later point in time. Further on the algorithm previously used to create the provided hash can also be extracted from the structure of the hash string.

    So actually what is happening is that both times the crypt() function hashes the password. And it uses the same salt both times. For the first time it used any salt string and some available algorithm, it does not matter which. That salt is embedded in the hash, the structure of the hash string points towards the applied algorithm. The second time it is able to use the same salt and the same algorithm again if the algorithm is still available , since it can be extracted from the old hash string to be reused. That way the created hash string will be identical exactly if 1. the same password is hashed and 2. the same algorithm is still available.

    Bottom line: that strategy is fine, it works, it allows for some robustness against changes in the underlying system over time without invalidating all older hashes. And it offers superior security, since you don't have to use a hard coded salt string over time.