How does bcrypt work?

Asked

Viewed 2,145 times

1

I don’t quite understand the workings of salt of bcrypt, in a javascript code (Node):

const bcrypt = require('bcrypt');
const saltRounds = 10;

async function init(plainPassword) {
    let salt = await bcrypt.genSalt(saltRounds);
    console.log(salt);

    let hashPassword = await bcrypt.hash(plainPassword, salt);
    console.log(hashPassword);

    let check = await bcrypt.compare(plainPassword, hashPassword);
    console.log(check);
}
init('cba');

I create a salt and use to create password hash, then I check the password using the function compare, however it is not passed the salt created previously, as then bcrypt makes this comparison, since the value of salt will interfere with the final hash of the password?

1 answer

2


The hashPassword includes the salt used. I practically answered this in another matter, but since the question wasn’t specifically about that, then I guess I can answer it here too.

The same way that "works on Node" is the same as it works anywhere, since it implements the same Bcrypt.


Bcrypt is deterministic. For this reason it will have the same result if using the same salt as any existing KDF. Bcrypt has three parameters:

  • Computational cost.
  • Salt.
  • Password.

There are other derivative functions (Kdfs) that have other input parameters. Argon2, for example, has individual cost parameters (for memory, iteration, etc.), output length parameters. On the other hand, HKDF does not have computational cost, because it is not intended for passwords, but has hash length, result length, flexible.

As a result will:

  • "Hash".

However, the first two values are public, in the case of Bcrypt. Therefore, Bcrypt itself uses a format, similar to Modular Crypt Format, where:

${Algoritmo}${Custo}${Salt}{Hash}

This way the hash is included in values that make it possible to compare the same hash, since we know the algorithm, the cost, the salt and the expected result.


A small test, with a result of:

$2b$10$111111111111111111111u1Fg3CCqE4CYDkm0w0C9gxJ.HpXSNHlS

You can get the algorithm, the cost, the salt and the "expected hash", therefore:

    let resultado = '$2b$10$111111111111111111111u1Fg3CCqE4CYDkm0w0C9gxJ.HpXSNHlS';

    let algo = resultado.substring(1, 3);
    let custo = resultado.substring(4, 6);
    let salt = resultado.substring(7, 29);

So just do the bcrypt.hash and you can compare the value, normally:

const crypto = require('crypto');
const bcrypt = require('bcrypt');

async function init(plainPassword) {
    let resultado = '$2b$10$111111111111111111111u1Fg3CCqE4CYDkm0w0C9gxJ.HpXSNHlS';

    let algo = resultado.substring(1, 3);
    let custo = resultado.substring(4, 6);
    let salt = resultado.substring(7, 29);

    let hash = await bcrypt.hash(plainPassword, '$' + algo + '$' + custo + '$' + salt);

    console.log(crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(resultado)));
}

init('cba');

See that there is none bcrypt.compare, the crypto.timingSafeEqual does the same as the ===, however it is safe against timing-Attack. That’s basically what the bcrypt.compare makes, when you include the hashPassword he already has the necessary data. :)

  • So, in this case, what is the function of salt?

  • @Guilhermecostamilam Prevent the same password from having the same result. If you have KDF(1,1), KDF(1,2), KDF(1,3) you will get different results, even if "1" is in both. This prevents pre-computation attacks, since to pre-compute the result of an X password you will have to compute all possibilities of SALT with X.

  • I was thinking about doing something like bcrypt.hash(plainPassword, salt+systemSalt) being systemSalt a constant salt in the system, makes a difference? Or bcrypt.hash(salt+plainPassword+systemSalt, salt)? Some of them can increase security?

  • This is called "Pepper". There are no significant gains in security, in Bcrypt this is worse, because salt is limited to 16 bytes. In other KDF (Argon2) you can have larger salt, so you can still have 32 bytes of salt and 32 bytes of Pepper, for example. You put a salt next to the password is a complete catastrophe. Bcrypt only supports passwords up to 72 bytes, so using 16 bytes will reduce the possibility to a maximum of 56 bytes. If you are still adding as a prefix, depending on the size, you will end up disabling the password. Use the algorithm as it was proposed, or use another algorithm.

  • @Guilhermecostamilam The "Pepper" makes sense if you are physically isolated, for example a database on one server, the web on another and perhaps another HSM. This way compromising one will not give all the information, but in Bcrypt this is more complicated, for lack of space.

  • A doubt, the saltRouds, the higher the number the longer the time/resources it will take to create the password, correct? But this increases the security to compensate? What would be more or less the ideal value that balances between the two?

  • Yes. This makes it harder to find the password through the hash, but easier to do a Dos, by using more server resources as well. The ideal depends on how often passwords are required and how long the response time is tolerated and also on the hardware. They usually recommend up to 1 second or up to 500ms. Set the highest cost where your server is able to meet within this time, usually anywhere from 14 to 17.

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.