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 tom
,a
transform toc
and so on. But it's only one way, ifp
transform tom
, doesn't mean thatm
will transform intop
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<a/b/x/y>$[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
, andsalt
, 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 with2b
-
Cost
is12
- 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!
Top comments (0)