How does BCrypt Verification work?

Have you ever wondered how can hashing algorithm verifies? Before we going into that, we have to know that our password that we used to enter on a website, is going through one-way hashing function, thanks to it, your password is only known by ourself, even the server's owner shouldn't know about it. Differs from encryption which is designed to be able to decrypt the contents, Hashing is only one way, that mean you hash it, you can't no longer retrieve it's original content. How does that work? How does hashing work? Before we learn about Bcrypt hashing and verification, I will try to explain to you a simple hashing concepts. Let's say you have a password, your password is pass123 Each letter will be then transform into another letter using some algorithms. For example pass123, p transform to m, a transform to c and so on. But it's only one way, if p transform to m, doesn't mean that m will transform into p Your password: pass123 might transform into D32rASDF and will always transfrom into D32rASDF everytime you enter pass123. So in practice, you can compare it, like these: if (hash(input_password) === hash(stored_password_in_database)) { // user login succeed } WAIT A MINUTE!, But in advance hashing algorithm like BCrypt, Argon2 and any other strong hashing algorithm will produce different hash result, even if you pass the same password. bcrypt("pass123") // result: $2a$12$G/bzOjiOAeFTHhvvImkRMeXQ9IiMK6Z38wcMs4H1W0wQ.ry7Loc1a bcrypt("pass123") // result: $2a$12$e1sitHZPq0KFuJ.G9dOmn.wKtfEwVp5O8qElZ.xzqOmQ5ZmA6BYm6 Then, this will never work again. if (bcrypt("pass123") === bcrypt("pass123)) { // user login always failed } How does it verify then? It actually pretty simple, instead of having your password passed straight into hash function and get result. It has another variable that stores random letters, this variable is called salt, this salt will help hashing algorithm makes more unpredictable result, they also has cost variable. Cost is the measure of the resources needed to calculate a hash. According to Wikipedia, this is the structure of bcrypt: $2$[cost]$[22 character salt][31 character hash] For example, with input password abc123xyz, cost 12, and a random salt, the output of bcrypt is the string $2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW \__/\/ \____________________/\_____________________________/ Alg Cost Salt Hash Where: $2a$: The hash algorithm identifier (bcrypt) 12: Input cost (2^12 i.e. 4096 rounds) R9h/cIPz0gi.URNNX3kh2O: A base-64 encoding of the input salt PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW: A base-64 encoding of the first 23 bytes of the computed 24 byte hash As you can see from above, BCrypt stores the algorithm, cost, and salt alongside the hash result. You couldn't get bcrypt to work with only the hash. Let me tell you this: If you had the algorithm, cost, and salt, you could get the same hashing result, this mean you could compare it Take a look at this code: import bcrypt from "bcryptjs"; // your input const inputPassword = "pass123"; // implement the function function bcryptHash(input) { const cost = 12; const salt = bcrypt.genSaltSync(cost); const hash = bcrypt.hashSync(inputPassword, salt); return hash; } // generate hash by calling the function // this result is then stored on the database const result = bcryptHash(inputPassword); // print to the console console.log(result); If I execute the same file, it will of course generate 2 different hash node index.js # result = $2b$12$XfT3eq.O3t.NeklIEbzgKOfLZZgHSrYSBKNk5.f5IngnP/Z6/Xo/u node index.js # result = $2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC But, it has some similarities, it starts with $2b$12$, which the algorithm followed by the cost, then the 22 letter after is the salt From the result, we now know that: Algorithm is start with 2b Cost is 12 We need to the salt // assumes this is the string of password that you get from the database const storedPassword = "$2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC"; // remove the prefix, then get the 22 letters of salt const salt = storedPassword.replace("$2b$12$", "").substring(0, 22); // salt = "FSmg5sXtB.XdJH2fFTrV7u" The salt is: FSmg5sXtB.XdJH2fFTrV7u Now, instead of generating a new salt, you assign the salt that is extracted, and combines it with algorithm and the cost at the suffix // this will give you fixed hash result function bcryptFixedHash(input) { // const cost = 12; // const salt = bcrypt.genSaltSync(cost); // combines the alg, cost and salt together const salt = "$2b$12$" + "FSmg5sXtB.XdJH2fFTrV7u"; const hash = bcrypt.hashSync(inputPassword, salt); return hash; } const inputPassword = "pass123"; const storedPassword = "$2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXh

Mar 21, 2025 - 01:18
 0
How does BCrypt Verification work?

Have you ever wondered how can hashing algorithm verifies?

Before we going into that, we have to know that our password that we used to enter on a website, is going through one-way hashing function, thanks to it, your password is only known by ourself, even the server's owner shouldn't know about it.

Differs from encryption which is designed to be able to decrypt the contents, Hashing is only one way, that mean you hash it, you can't no longer retrieve it's original content. How does that work?

How does hashing work?

Before we learn about Bcrypt hashing and verification, I will try to explain to you a simple hashing concepts.

  1. Let's say you have a password, your password is pass123
  2. Each letter will be then transform into another letter using some algorithms. For example pass123, p transform to m, a transform to c and so on. But it's only one way, if p transform to m, doesn't mean that m will transform into p

Your password: pass123 might transform into D32rASDF and will always transfrom into D32rASDF everytime you enter pass123. So in practice, you can compare it, like these:


if (hash(input_password) === hash(stored_password_in_database)) {
  // user login succeed
}

WAIT A MINUTE!, But in advance hashing algorithm like BCrypt, Argon2 and any other strong hashing algorithm will produce different hash result, even if you pass the same password.


bcrypt("pass123")
// result: $2a$12$G/bzOjiOAeFTHhvvImkRMeXQ9IiMK6Z38wcMs4H1W0wQ.ry7Loc1a

bcrypt("pass123")
// result: $2a$12$e1sitHZPq0KFuJ.G9dOmn.wKtfEwVp5O8qElZ.xzqOmQ5ZmA6BYm6

Then, this will never work again.

if (bcrypt("pass123") === bcrypt("pass123)) {
  // user login always failed
}

How does it verify then?

It actually pretty simple, instead of having your password passed straight into hash function and get result. It has another variable that stores random letters, this variable is called salt, this salt will help hashing algorithm makes more unpredictable result, they also has cost variable. Cost is the measure of the resources needed to calculate a hash.

According to Wikipedia, this is the structure of bcrypt:

For example, with input password abc123xyz, cost 12, and a random salt, the output of bcrypt is the string

$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
\__/\/ \____________________/\_____________________________/
Alg Cost      Salt                        Hash

Where:

  • $2a$: The hash algorithm identifier (bcrypt)
  • 12: Input cost (2^12 i.e. 4096 rounds)
  • R9h/cIPz0gi.URNNX3kh2O: A base-64 encoding of the input salt
  • PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW: A base-64 encoding of the first 23 bytes of the computed 24 byte hash

As you can see from above, BCrypt stores the algorithm, cost, and salt alongside the hash result. You couldn't get bcrypt to work with only the hash.

Let me tell you this:

If you had the algorithm, cost, and salt, you could get the same hashing result, this mean you could compare it

Take a look at this code:

import bcrypt from "bcryptjs";

// your input
const inputPassword = "pass123";

// implement the function
function bcryptHash(input) {
    const cost = 12;
    const salt = bcrypt.genSaltSync(cost);
    const hash = bcrypt.hashSync(inputPassword, salt);

    return hash;
}

// generate hash by calling the function
// this result is then stored on the database
const result = bcryptHash(inputPassword);

// print to the console
console.log(result);

If I execute the same file, it will of course generate 2 different hash

node index.js
# result = $2b$12$XfT3eq.O3t.NeklIEbzgKOfLZZgHSrYSBKNk5.f5IngnP/Z6/Xo/u
node index.js
# result = $2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC

But, it has some similarities, it starts with $2b$12$, which the algorithm followed by the cost, then the 22 letter after is the salt

From the result, we now know that:

  1. Algorithm is start with 2b
  2. Cost is 12
  3. We need to the salt

// assumes this is the string of password that you get from the database
const storedPassword = "$2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC";

// remove the prefix, then get the 22 letters of salt
const salt = storedPassword.replace("$2b$12$", "").substring(0, 22);

// salt = "FSmg5sXtB.XdJH2fFTrV7u"

The salt is: FSmg5sXtB.XdJH2fFTrV7u

Now, instead of generating a new salt, you assign the salt that is extracted, and combines it with algorithm and the cost at the suffix


// this will give you fixed hash result
function bcryptFixedHash(input) {
    // const cost = 12;
    // const salt = bcrypt.genSaltSync(cost);

    // combines the alg, cost and salt together
    const salt = "$2b$12$" + "FSmg5sXtB.XdJH2fFTrV7u";

    const hash = bcrypt.hashSync(inputPassword, salt);

    return hash;
}


const inputPassword = "pass123";

const storedPassword = 
"$2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC";

const inputHash = bcryptFixedHash(inputPassword);

// now you get the exact same hash
// inputHash = $2b$12$FSmg5sXtB.XdJH2fFTrV7uBVKaiSdueu7FUcGSTXhoBnPssrjPvmC

Comparing the hash

Now that you get the same hash, you could compare it to verify the user


if (storedPassword === inputHash) {
    // user login succeed
}

In addition, you could use safe compare equal rather than === to compare, but that wouldn't be neccesary since the bcrypt hash result is always different and thus irrelevant to timing attack or similar.

Thank you!