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?

1 Answer
1

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.