10

I am using Blowfish with PHP crypt() for password hashing but I noticed something weird. Quoting PHP documentation:

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

I noticed that the salt that gets included in the final hash is 1 character short (the last one is cut off) as if the salt was too long, but that is not the case.

Example of my script output:

Salt: 97504ebb48c4619f820f83 with length 22

Blowfish: $2a$13$97504ebb48c4619f820f8u4QTtlV5MoqHt9l7hmK4jEohUXrI.0PK

Hash match.

As you can see, the random salt is exactly 22 digits but the '3' is missing in the final hash. If I make the salt 21 chars only I get a corrupted hash and it does not work. So why does it trim the last char?

Examples on PHP manual also add a final $ to the random salt. Is that $ there for a reason or they just randomly added it to Blowfish, SHA-256 and SHA-512 to confuse everyone?

And finally, this is my code:

if (CRYPT_BLOWFISH == 1) {
    $salt = md5(uniqid(rand(), TRUE));
    $salt = substr($salt, 0, 22);

    echo "Salt: " . $salt . " with length " . strlen($salt) . "<br />";
    $pass = "rasmuslerdorf";

    $bsalt = "$2a$13$".$salt;
    $blowfish=crypt($pass, $bsalt);
    echo 'Blowfish:     ' . $blowfish . "<br />";

    if (crypt($pass, $blowfish) == $blowfish) {
        echo "Hash match.<br />";
    }
    else echo "no<br />";
}
else {
    exit("You need php 5.3 or newer");
}
1
  • There was a discussion about the unused bits, and how the stored salt will look. Sep 30, 2012 at 15:32

1 Answer 1

18

Actually the '3' is there, but it is called 'u'.

Explanations: bcrypt needs a 128-bit salt. The salt you provide should be in "modified base64", i.e. consisting in letters, digits, '/' or '.' signs (this is "modified" because in true Base64, the '+' sign is used instead of '.', and the order is different). Since this is a 64-element alphabet, each character is worth 6 bits, and your 22 characters encode 132 bits.

Bcrypt only uses the first 128 bits of your 132 bits, so the last four bits are totally ignored. The last four bits are the last four bits of the last salt character. In the modified base64, 'u' has value 48, 110000 in binary, while '3' has value 57, 111001 in binary. As you can see, the two values differ only in their last (rightmost) four bits, which are ignored.

What happens is that the bcrypt implementation you use first converts your salt (22 characters) into a 128-bit buffer (16 bytes), and that's where the drop occurs. Then the code will convert it back to modified base64, but this time it will use zeros for the "missing four bits", thus turning your '3' into a 'u'. If you use '97504ebb48c4619f820f8u' instead of '97504ebb48c4619f820f83' as salt, you will find that you obtain the exact same bcrypt output, because these two salts differ only in the last four bits, which are ignored.

2
  • Nice and short! Sep 30, 2012 at 15:36
  • 1
    Yes, sometimes I succeed at remaining short. Sep 30, 2012 at 15:53

You must log in to answer this question.

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