I am doing a project with Angular and Express. I noticed something strange with bcrypt.js
's encrpytion. Let's say that there are 2 users called "User 1" and "User 2" and their password is "Password@1234", so they are the same. When I sumbit User 1's sign in form and then I do the same with User 2, I noticed something really weird with the encryption on the server side:
User1
Register password: Password@1234
Encrypted above pwd: $2a$12$MMZrwIu0oheKkbzz1rOS2ul4JxXRRYTh3VU0R3ItzI9DTQ.FrGsWO
========================================================================================
User2
Register password: Password@1234
Encrypted above pwd: $2a$12$IAbbkcm5oGinBuyfLNnEJupuU6gJ65kLvrromT7Pb.YybyMcipege
Now, as you can see, the passwords (not the encrypted ones) are the same. But why is the encryption different? If I try to log in with one of those 2 users, with the same password, that it gets encrypted on the client side, it says that password is wrong.
Here is the code:
FRONT SIDE
sign-up.component.ts
signUp(): void {
if (this.signUpForm.valid) {
const formData = this.signUpForm.value;
this.httpClient.post('http://localhost:3000/sign-up', formData).subscribe(
(response) => {
const message = (response as SignUpResponse).message;
this.appendAlert(message, "success", 1);
this.signUpSuccess = true;
},
(error: HttpErrorResponse) => {
this.appendAlert(error.message, "danger", 1)
}
);
}
}
log-in.component.ts
import * as bcrypt from 'bcryptjs';
export class LogInComponent{
encryptPassword(password: string): string {
const saltRounds = 12;
const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(password, salt);
return hash;
}
logIn(): void {
if (this.logInForm.valid) {
const formData = this.logInForm.value;
formData.userPwd = this.encryptPassword(formData.userPwd);
this.httpClient.post('http://localhost:3000/log-in', formData).subscribe(
(response) => {
const message = (response as LogInResponse).message;
this.appendAlert(message, "success", 1);
this.logInSuccess = true;
},
(error: HttpErrorResponse) => {
this.appendAlert(error.error.message, "danger", 1)
if (error.error.message.includes('Account locked')) {
this.logInForm.disable();
setTimeout(() => this.logInForm.enable(), 300000); // 5 minutes
}
}
);
}
}
}
SERVER SIDE
sign-up.js
"use strict"
const express = require('express');
const router = express.Router();
const connection = require('./db/connection');
const crypto = require('crypto');
const nodemailer = require('nodemailer');
const bcrypt = require('bcryptjs');
//Encrypt password method
function encryptPassword (password) {
const saltRounds = 12;
const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(password, salt);
return hash;
}
// Sign-up endpoint
router.post('/sign-up', (req, res) => {
const { userName, userEmail, userContact, userDOB, userPwd} = req.body;
if (!userName || !userEmail || !userContact || !userDOB || !userPwd ) {
return res.status(400).json({ error: 'All fields are required' });
}
const encryptedPwd = encryptPassword(userPwd);
const verificationPin = crypto.randomInt(100000, 1000000).toString();
const isVerified = 0;
console.log("Register password: "+userPwd);
console.log("Encrypted above pwd: "+encryptedPwd);
const sql = 'INSERT INTO users (name, email, contactNumber, dob, pwd, verificationPin, isVerified) VALUES (?, ?, ?, ?, ?, ?, ?)';
const userValues = [userName, userEmail, userContact, userDOB, encryptedPwd, verificationPin, isVerified];
connection.execute(sql, userValues, (err, _results) => {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Error while inserting data' });
} else {
sendEmail(userEmail, userName, verificationPin, (emailError) => {
if (emailError) {
return res.status(500).json({ message: 'Something went wrong while sending email' });
} else {
return res.status(200).json({ message: 'User has been created successfully! Now check your email to insert PIN below' });
}
});
}
});
});
module.exports = router;
log-in.js
// Log-in endpoint
router.post('/log-in', (req, res) => {
const { userName, userPwd } = req.body;
const sql = 'SELECT * FROM users WHERE name = ?';
connection.execute(sql, [userName], (err, results) => {
if (err) {
console.error('Error while logging: ', err);
return res.status(500).json({ error: 'Oops! Something went wrong! Please try again later.' });
} else {
if (results.length === 0) {
return res.status(400).json({ message: `Username doesn't exist! You may sign up or try again.` });
} else {
const user = results[0];
console.log(results);
const now = new Date();
if (user.lock_until && now < new Date(user.lock_until)) {
return res.status(400).json({ message: `Account locked. Try again later. Account will be unlocked at ${user.lock_until}`});
} else {
console.log("Entered pwd: "+userPwd);
console.log("Database pwd: "+user.pwd)
if (bcrypt.compareSync(userPwd, user.pwd)) {
connection.execute('UPDATE users SET failed_attempts = 0, lock_until = NULL WHERE name = ?', [userName]);
return res.status(200).json({ message: 'You successfully logged in!' });
}
else {
let failedAttempts = user.failed_attempts + 1;
let lockUntil = null;
if (failedAttempts >= 5) {
lockUntil = new Date(now.getTime() + 5 * 60000); // 5 minutes lock
}
connection.execute('UPDATE users SET failed_attempts = ?, lock_until = ? WHERE name = ?', [failedAttempts, lockUntil, userName]);
return res.status(400).json({ message: `Password doesn't match! ${5 - failedAttempts} attempts remaining!` });
}
}
}
}
});
});
How can I make sure that the encrypted passwords are the same?
Any help would be appreciated!
As @WiktorZychla said, different salt gives different hash despite same password.
Thanks to @hoangdv's comment, I realized that I was using bcrypt.compareSync()
wrongly: because the first two parameters of the method are the non-encrypted password and the encrypted password. Instead, I was inserting as parameters the password I was encrypting in the client side and then the DB password.
So, as @hoangdv said, I should delete:
formData.userPwd = this.encryptPassword(formData.userPwd);
in the client side. And it worked!