2

I am reading up on PHP's crypt() function. I have a few questions on it -

  1. What is the significance of the trailing '$' sign at the end of the salt string that I see in most examples. The manual doesn't specifically say anything about ending the salt string with it.

  2. Is there anyway I can extract only the salt portion from the hash? I know that I probably don't need to, since the crypt() function will internally do it when doing a comparison. But just for the heck of it. Just for me to see the salt. For egs consider this code

$pass = 'secret'; $salt = '$2y$07$usesomesillystringforsalt$'; echo crypt($pass, $salt);

The output of this is $2y$07$usesomesillystringforex.u2VJUMLRWaJNuw0Hu2FvCEimdeYVO and I am unsure about the boundary between the salt and the hash. Is the 'e' in the 'forex' sub string part of the salt or the hash? It would be much easier if I could just extract the salt part of it.

  1. Also the crypt() manual says

... Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z"....

As per this I expect 22 characters after the $ sign following the cost parameter. But consider this code

$pass = 'secret'; $salt = '$2y$07$somesillystring$'; echo crypt($pass, $salt);

The output of this is $2y$07$somesillystring$$$$$$.O6JLPmGlDvy4BicGmkuBD.DN8OYiIoG. My question is why is it padded only up to 21 characters following the $ sign after the cost parameter. I was expecting it to be padded up to 22 characters.

1
  • The crypt() function is not just in PHP - it's part of the POSIX standard and accessible from many languages.
    – symcbean
    Mar 12, 2014 at 9:59

1 Answer 1

3

The 22 characters it requires are actually encoded binary data that is used for the salt. Bcrypt/Blowfish/$2y$ only uses the first 128 bits of your 132 bits, so the last four bits are totally ignored and should not be a dollar sign because that is not a valid character for the modified base64 encoding that is used for the salt. What has happened is you specified too short a salt, so it was padded with $ so the system knows the salt has ended. The reason it ends up being a . is because since only the first four bits are used, the last four are padded with zeroes before the beginning of the encoded hash. A . is all zeroes in this base64 type encoding and because there's no previous value to affect the first four bits of the 22nd character, you end up with just the .. As an aside it does make me wonder why they chose to use dollars instead of . to represent the missing salt - perhaps just to differentiate between actually asking for zeros in the salt and the automatic decision to pad with them.

$pass = 'secret';
echo crypt($pass, '$2y$07$notlongenough'), "\n";
echo crypt($pass, '$2y$07$longenoughsortof......');

> $2y$07$notlongenough$$$$$$$$.uYiNG9WczQSWgnuPdcNj0jugGbSnaAC
> $2y$07$longenoughsortof......9dPQFbAgDCQ4RWEcZ4/ficmGapMNSpC

It only makes sense to provide one of: ., O, e, or u for the last character of the salt because these are the only characters in the encoding set that actually affect the first four bits. FYI the full range is [./A-Za-z0-9] in that order, so if you number them 0-63 and look at the binary expression of those numbers you'll see where those four characters come from.

The examples seems to erroneously use trailing dollars. They are not needed and indeed shouldn't really be present. All you need to do is provide enough bits for the salt depending on the algorithm you're using. Every character should be as random as possible up until enough bits have been provided (i.e. a minimum of 22 characters for Blowfish/$2y$).

The output is then a concatenation of: algo-description, salt used and hashed result/password. The algorithm knows the binary boundaries of where the salt ends and the hashed string begins. There should not be any dollar signs with $2y$ because you should have provided a long enough salt string which will not consist of any dollar signs (again, because that is not in the valid character range for the utilised modified base64 encoding).


This should be enough information for you to extract the salt if you so desire. But PHP actually recommends the use of password_hash, as do I if you have v >=5.5.0 https://www.php.net/manual/en/function.password-hash.php.

This will automatically generate a secure random salt for you using either /dev/urandom or CryptGenRandom() on Windows - both well vetted to be sufficient for this purpose. If you've already started using crypt, password_hash is compatible so fear not about upgrading to use it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .