Crypt for Password Hashing. Blowfish Produces Weird Output

Crypt for password hashing. Blowfish produces weird output

The reason that you're seeing problems is that it doesn't actually use 22 characters of salt. It only uses 21.25 characters. So a few bits of the 22nd character are used for salt, and the remaining are used for hash (the result).

The reason is that the salt isn't a string. It's a 128 bit number. The number is serialized into base64. To review how base 64 works, every 3 byte block is "translated" into a 4 byte block.

[byte1][byte2][byte3]
[new1][new2][new3][new4]

Now, remember that each original byte has 8 bits. Therefore, each "new byte" will only have 6 bits (because we aren't adding information, we're just representing it differently).

So what's happening is that you're only providing 21 characters of data. Which translates to 15.75 bytes when decoded. But you can't have a partial byte. So the last decoded block is thrown away (due to having insufficient info). And those 6 bits that we threw away map completely to the 21st character.

Therefore, without the 2 bits that are used from the 22nd character, the 21st must be thrown away (because partial bytes don't make sense).

We can test this out:

$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
for ($i = 0; $i < strlen($chars); $i++) {
echo crypt('string that should be hashed', '$2y$08$12345678901234567890' . $chars[$i]) . "\n";
}

Produces:

$2y$08$123456789012345678900.iIlIFEGaqDj6XbnKkK1F14HmMGLV.mu
$2y$08$123456789012345678901.iIlIFEGaqDj6XbnKkK1F14HmMGLV.mu
$2y$08$123456789012345678902.iIlIFEGaqDj6XbnKkK1F14HmMGLV.mu
.
.
.
$2y$08$12345678901234567890/.iIlIFEGaqDj6XbnKkK1F14HmMGLV.mu

But if we add a 22nd byte (irrespective of what it is):

$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
for ($i = 0; $i < strlen($chars); $i++) {
echo crypt('string that should be hashed', '$2y$08$12345678901234567890' . $chars[$i] . 'a') . "\n";
}

Then we get the effect of the random distribution:

$2y$08$123456789012345678900OtUUu.EAOjrOztGKf2m.TZIe7HGzFgF.
$2y$08$123456789012345678901Ou28wcnld1gB2vjW9obdQdz6kLMasqKC
$2y$08$123456789012345678902Oum7Yp/p4TEeEC5JxsmnQsACNnnK0cv2
$2y$08$123456789012345678903OxMer1AD.P.UpAMlykl5SokMmDM1BU0W
$2y$08$123456789012345678904OpoNDsh7DaAoSjiZFJKO7iMy53BqwsjO
$2y$08$123456789012345678905OQRUqlnlEpBzccxrCgyZVtl6a.tQxNz6
$2y$08$123456789012345678906O6QMFdYZ.tvQpSdYaxlFl1Rlsk05/Aym
$2y$08$123456789012345678907OwF1TKI.OYT3xtBxg8tqex4L8mZttUCm
$2y$08$123456789012345678908OtzJXaS8/x0KYQ2epPRgVSjWSy/yAwMK
$2y$08$123456789012345678909O17D/xQeJGLIzpwBZuN2kxdpxi6p3aDq
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890bOD9Z5cUlQgJtvhqSOIK/3BV/1QIEmHby
$2y$08$12345678901234567890cOG5DxIU4B/ftl01V/MhViyi8YymLKEdC
$2y$08$12345678901234567890dOcd0.C8PVpjqW7oGI9AZuTVjNwxZDDpa
$2y$08$12345678901234567890eOLQSg5zmHm2nOCmRMdNeY8LxW1xMKnwm
$2y$08$12345678901234567890fOI.DZa4KuxngvaBT8JFtRWY8oRs9A266
$2y$08$12345678901234567890gOTA9XsdwxujLBdLaypPHehWjj1GyjDRC
$2y$08$12345678901234567890hOkS/cZmSqtdHSWz3zPkImTfZbHvdC8Wm
$2y$08$12345678901234567890iOXDaVzn/h7/oQtUgHyPodyggGkOqxFdW
$2y$08$12345678901234567890jODbaT2pRSwnD2qHm43YdAbHVPBJ8iapi
$2y$08$12345678901234567890kOrUyng3J5OCChkP6tHiM.rz4o4CdPkTO
$2y$08$12345678901234567890lOtscWm7fnlUJXZIXLKhVI7E2Abh7uc3i
$2y$08$12345678901234567890mOCeJM40E/G0WrJ4utkSaJwtZUMCae326
$2y$08$12345678901234567890nO4ac8AzrsXk6HpAtOaGEvGfS8eceFtSC
$2y$08$12345678901234567890oOv3BFJmdPMx9josbfOHHtu/7xgoGUygq
$2y$08$12345678901234567890pOPWQlIGa.WBx8kDEEG05uWhUioyNqWiq
$2y$08$12345678901234567890qOg2ufL5bmYfAoZEFknsRaSOlI4GVBKWy
$2y$08$12345678901234567890rOJZTvmghag6zIY5Ha7iOCgArPZGotche
$2y$08$12345678901234567890sOZjZ2OaVZy.GeXp/BQvjbCpXgNa/GAlK
$2y$08$12345678901234567890tO3bAZAMEXEZm72/mAkbJkefUua9CUFuy
$2y$08$12345678901234567890uOQ.i2vydj6OGyl84Qhg5OXPq7OkRQomu
$2y$08$12345678901234567890vOc9BKZfLu6mcd2mIfLtmT6C6JwDT.Siq
$2y$08$12345678901234567890wO7ow2JgV.7yzEsllHUbhbMrOMKXSihsq
$2y$08$12345678901234567890xOUI89zc5eDCCCHoTljMyXuGXmIz9b0PW
$2y$08$12345678901234567890yORmKbjoeO.1HSpQB7L5EBMSRjJr4lR62
$2y$08$12345678901234567890zOZkhGY/cILtgQRmHLkx//nuzLXSwLqYy
$2y$08$12345678901234567890AOuJWX5/tdzRCTTs5EXYioLP1t7u1Ao7u
$2y$08$12345678901234567890BO2vHWuKdbL2lsbBQwaAkWCXz/YVEaHP2
$2y$08$12345678901234567890COedKIdK.eAjm2zF0CAnuM9XxbO3CakoK
$2y$08$12345678901234567890DOpunwAyx9X4/tJzDmUXARABluQdRV7Ji
$2y$08$12345678901234567890EOB1ONHz9lELb7iUvtzTi.PTSgN2tFv1.
$2y$08$12345678901234567890FOplAZBguPKXbAQDxq9PXqgjH/1ZX6u7C
$2y$08$12345678901234567890GOP/G3kfN/r92DIQlC0eVyGi3jWRUoVXK
$2y$08$12345678901234567890HOmala7V1QCL7PX79yODRg2Y5lTq6i/ii
$2y$08$12345678901234567890IOWbq1AXhTucizWIBn58rgVYFpRxMpm8.
$2y$08$12345678901234567890JOxgmM1XAcDg7AUpzeHzHxn6z75ljNoDy
$2y$08$12345678901234567890KOTnfd7pzmfzf80CrXxWC24sK3y1DAbb6
$2y$08$12345678901234567890LOXxQX37TiNlNMfZUtMLZFrZah8u39q9K
$2y$08$12345678901234567890MOmpvWu3ZKbbilLb4f8QF6OUPPpEbsM42
$2y$08$12345678901234567890NO8VjZ2KNbOVoOzgP/Tjd6IFtwjRG2PJ2
$2y$08$12345678901234567890OOvSnZoahC5g1Ewlm6K7US13i6vJIQSqm
$2y$08$12345678901234567890POVs5m/8eCyLd11zjEPYoYhpaZAz6PYF2
$2y$08$12345678901234567890QOk4MBZhDwzS8dwJl6lm.hdAVBcllSid2
$2y$08$12345678901234567890ROWh4H3TuKSuFfrtx1vqHnU/RrQ0HrbNW
$2y$08$12345678901234567890SOd/USMzVBx6wyPgsuvAszCIVZ6zOA44O
$2y$08$12345678901234567890TO53YobspFDSFshtGX9hH4LTw2OT2T4P.
$2y$08$12345678901234567890UOMLp7HSCxWMMxgJVN6JTN7WRKlRPN17y
$2y$08$12345678901234567890VOmOMGgpLXOV/mft8WXOWXmQjc71SN6g2
$2y$08$12345678901234567890WOiAkYTQmitOHabdScoZivJ4JeKtJ6t7.
$2y$08$12345678901234567890XOUUqRtGjd/nob.UiRrJvFyKSMELAIuZe
$2y$08$12345678901234567890YOukccL1Y2PDV9ErOLHileZOq5m6zIzSy
$2y$08$12345678901234567890ZOMNrfK..n1YjuP3F.S4Taxn0XvIf5gXW
$2y$08$12345678901234567890.OmG2XbJMpLDBrtq44ptVtXkVaGdAT9oO
$2y$08$12345678901234567890/OTN4hG/XcY.FtrT85TGI.Vm0sH0tpQ.a

Now, to prove that we're only using a few bits from the last byte, let's vary that one, holding the 21st fixed:

$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
for ($i = 0; $i < strlen($chars); $i++) {
echo crypt('string that should be hashed', '$2y$08$12345678901234567890a' . $chars[$i]) . "\n";
}

Here it is:

$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890aeDDHXF42QK8mY.t4/x9I.DNpdmARsDG.
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890auYqXTg7.1WNKn8Yxc4wW2p2ppsJb9rZa
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890aOQs9arCVhxFtQ.Z7yJUOtp8UCDsR1rHa
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO
$2y$08$12345678901234567890a.FpXM0OnHV1fSxeBCiU8eEDae5LtBtAO

Notice how that only produces 4 unique hashes? That's because we're only using the first 2 bits (2^2) of that last byte. The rest are actually part of the result hash (and hence thrown away).

Make sense?

And BTW: for this and other reasons, I would suggest not using crypt() directly, but instead using a library. Such as password which is coming in PHP 5.5, or it's compatibility library (which I maintain) password_compat.

Why does my PHP Blowfish encryption produce such weird results?

Check out http://us2.php.net/crypt, example #3 "Using crypt() with different hash types". In the example output it shows that the salt strings are also part of the encrypted value, so your issue with the salt being part of the encryption appears to be by design.

Why does my PHP Blowfish encryption produce such weird results?

Check out http://us2.php.net/crypt, example #3 "Using crypt() with different hash types". In the example output it shows that the salt strings are also part of the encrypted value, so your issue with the salt being part of the encryption appears to be by design.

Crypt function outputting two different values depending on PHP version

This is not so much an issue as you may think.

First you should note, that your code is not absolutely correct, BCrypt requires a 22 character salt, but you provided a 20 character salt. This means that the terminating '$' (which is not necessary btw) will be seen as part of the salt, as well as the first letter of your password. The $ is not a valid character for a BCrypt salt though.

Another thing to consider is that not all bits of character 22 are used, this is due to the encoding, ircmaxell gave a good explanation about this. So different salts can result in the same hash, you can see this well in this answer. How different implementations handle this last bits of the character 22 could theoretically change. As long as the crypt function can verify the password with both hashes there is no problem.

The generation of the salt with its pitfalls is one of the reasons, why the functions password_hash() and password_verify() where written, they make the password handling much easier.

Providing a salt for encryption with 'crypt()' isn't affecting the output + questions on crypt

The crypt() function on your system does not support the "2y" algorithm. At least Linux GLIBC 2.7 based systems only know DES, $2a$ (blowfish), $5$ (SHA-256) and $6$ (SHA-512). Therefore, the crypt() function assumed DES and only took the first two characters "$2" as salt. That of course produced always the same output.

Try SHA-512 for secure password hashes:

$salt_chars = array_merge(range('A','Z'), range('a','z'), range(0,9));
$salt = '$6$';
for($i=0; $i < 8; $i++) { $salt .= $salt_chars[array_rand($salt_chars)]; }
echo "salt=$salt\n";

$crypt = crypt('test', $salt);
echo "crypt=$crypt\n";

Regarding your second question, you can chose the algorithm by starting the salt with e.g. "$2a$" (instead the $6$ above) for blowfish. Read "man 2 crypt" for details. For some algorithms you can also encode more parameters like "rounds=4000" into the salt.

According the the crypt() manpage, the salt may be up to 16 characters following the $id$.
Longer salts will silently be truncated and produce the same output as for only the first 16 characters.

BTW, even in /etc/shadow, passwords only use 8 characters of salt with the SHA-512 algorithm. As the salt is only to make rainbow table attacks harder, this seems sufficient.

Why does crypt/blowfish generate the same hash with two different salts?

After some experimentation, I have come to the conclusion that this is due to the way the salt is treated. The salt is not considered to be literal text, but rather to be a base64 encoded string, such that 22 bytes of salt data would actually represent a 16 byte string (floor(22 * 24 / 32) == 16) of salt. The "Gotcha!" with this implementation, though, is that, like Unix crypt, it uses a "non-standard" base64 alphabet. To be exact, it uses this alphabet:

./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$

The 65th character, '$', is the padding character.

Now, the crypt() function appears to be capable of taking a salt of any length less than or equal to its maximum, and silently handling any inconsistencies in the base64 by discarding any data that doesn't make up another full byte. The crypt function will fail completely if you pass it characters in the salt that are not part of its base64 alphabet, which just confirms this theory of its operation.

Take an imaginary salt '1234'. This is perfectly base64 consistent in that it represents 24 bits of data, so 3 bytes, and does not carry any data that needs to be discarded. This is a salt whose Len Mod 4 is zero. Append any character to that salt, and it becomes a 5 character salt, and Len Mod 4 is now 1. However, this additional character represents only six bits of data, and therefore cannot be transformed into another full byte, so it is discarded.

Thus, for any two salts A and B, where

   Len A Mod 4 == 0 
&& Len B Mod 4 == 1 // these two lines mean the same thing
&& Len B = Len A + 1 // but are semantically important separately
&& A == substr B, 0, Len A

The actual salt used by crypt() to calculate the hash will, in fact, be identical. As proof, I'm including some example PHP code that can be used to show this. The salt constantly rotates in a seminon-random way (based on a randomish segment of the whirlpool hash of the current time to the microsecond), and the data to be hashed (herein called $seed) is simply the current Unix-Epoch time.

$salt = substr(hash('whirlpool',microtime()),rand(0,105),22);
$seed = time();
for ($i = 0, $j = strlen($salt); $i <= $j; ++$i) {
printf('%02d = %s%s%c',
$i,
crypt($seed,'$2a$07$' . substr($salt, 0, $i)),
$i%4 == 0 || $i % 4 == 1 ? ' <-' : '',
0x0A
);
}

And this produces output similar to the following

00 = $2a$07$$$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
01 = $2a$07$e$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
02 = $2a$07$e8$$$$$$$$$$$$$$$$$$$.WEimjvvOvQ.lGh/V6HFkts7Rq5rpXZG
03 = $2a$07$e89$$$$$$$$$$$$$$$$$$.Ww5p352lsfQCWarRIWWGGbKa074K4/.
04 = $2a$07$e895$$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
05 = $2a$07$e8955$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
06 = $2a$07$e8955b$$$$$$$$$$$$$$$.2UumGVfyc4SgAZBs5P6IKlUYma7sxqa
07 = $2a$07$e8955be$$$$$$$$$$$$$$.gb6deOAckxHP/WIZOGPZ6/P3oUSQkPm
08 = $2a$07$e8955be6$$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
09 = $2a$07$e8955be61$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
10 = $2a$07$e8955be616$$$$$$$$$$$.hWHhdkS9Z3m7/PMKn1Ko7Qf2S7H4ttK
11 = $2a$07$e8955be6162$$$$$$$$$$.meHPOa25CYG2G8JrbC8dPQuWf9yw0Iy
12 = $2a$07$e8955be61624$$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
13 = $2a$07$e8955be616246$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
14 = $2a$07$e8955be6162468$$$$$$$.OTzcPMwrtXxx6YHKtaX0mypWvqJK5Ye
15 = $2a$07$e8955be6162468d$$$$$$.pDcOFp68WnHqU8tZJxuf2V0nqUqwc0W
16 = $2a$07$e8955be6162468de$$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
17 = $2a$07$e8955be6162468deb$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
18 = $2a$07$e8955be6162468deb0$$$.aNZIHogUlCn8H7W3naR50pzEsQgnakq
19 = $2a$07$e8955be6162468deb0d$$.ytfAwRL.czZr/K3hGPmbgJlheoZUyL2
20 = $2a$07$e8955be6162468deb0da$.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
21 = $2a$07$e8955be6162468deb0da3.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
22 = $2a$07$e8955be6162468deb0da3ucYVpET7X/5YddEeJxVqqUIxs3COrdym

The conclusion? Twofold. First, it's working as intended, and second, know your own salt or don't roll your own salt.

Weird issue regarding my hashing function

Two things jump out:

  1. The salt used for Bcrypt has a limited "alphabet" of [./A-Za-z0-9], so a hyphen should not be used.
  2. The salt should be 22 characters long.

Lastly, IMHO, because you're using a salt with Bcrypt you don't really need another salt to pad the password.

The following function should work just fine:

function hashing($password, $salt, $cost = 12)
{
assert(preg_match('/^[A-Za-z0-9.\/]{22}$/', $salt);
return crypt($password, '$2y$' . str_pad($cost, 2, '0', STR_PAD_LEFT) . '$' . $salt);
}

That said, you should be using the password hashing API which has been included into the core since 5.5. For older versions you can use password compat which implements the same interface.

PHP bcrypt gives really short output for some reason

Well according to the manual: http://php.net/manual/en/function.crypt.php
it will return a "string that is shorter than 13 characters and is guaranteed to differ from the salt" if it errors. So that could be what's going on?

Try checking if CRYPT_BLOWFISH == 1 on your system.

Alternatively just use password_hash() as that should handle the salt generation for you.

What is the correct format for a blowfish salt using PHP's crypt?

The number following the 2a specifies the log2 of the number of rounds to perform. For example, 10 means do 1024 rounds. Usually, 10 is normal. Don't use numbers that are too big, or your password will take forever to verify.

See Why does BCrypt.net GenerateSalt(31) return straight away? for something related. :-)



Related Topics



Leave a reply



Submit