The One Hash Function You Need, BCrypt
CoddingAddicts
Posted on February 18, 2022
Whether you are building authentication for your app or designing a communication scheme, one thing is sure, you need hashes. And we got you the best kind. The scalable kind with a pinch of salt!.
bcrypt is a password-hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher and presented at USENIX in 1999.
Technicalities aside, this algorithm has two very important properties.
One, it employs a salt-mechanism to mitigate Rainbow Table attacks. And two, it introduces control over computation-time cost to scale with processing power and keep Dictionary attacks within practically-infeasable range.
This exposition targets JS users. But keep in mind that there is a Python implementation.
To get you started, you can install the package by typing,
npm install bcrypt
From there you can import the library using,
// ES5 syntax
const bcrypt = require('bcrypt')
// ES6 syntax
import bcrypt from 'bcrypt'
The package is relatively small and has only essential features. Exposed to us in two flavor, both synchronous and asynchronous, these are,
1. Salt Generation
// Sync
const salt = bcrypt.genSaltSync(rounds)
// Async
bcrypt.genSalt(rounds).then(function(salt){
// Use the salt
})
2. Hashing
// Sync
const hash = bcrypt.hashSync(plainMessage, salt)
/* Hash with auto-generated salt */
const hash = bcrypt.hashSync(plainMessage, saltRounds)
// Async
bcrypt.hash(plainMessage, saltRounds).then(function(hash) {
// Use the hash
})
3. Verification
// Sync
bcrypt.compareSync(testMessage, hash)
// Async
bcrypt.compare(testMessage, hash).then(function(result) {
// result == true or false
})
It might be surprising but this is all there is to Bcrypt.js. Pretty simple, huh!
Now to help you better see this in action. Here is a quick example of how a simple authentication scheme using Express.js and Mongoose.js would look like.
We will make a simple Node.JS backend. Express will handle the requests while, Mongoose will be used to store the user data. And before any of that, make sure you created a npm project and have both packages installed along with Bcrypt(as shown above).
From here, it is a 3+0-step work.
Step ZERO
We lay the structure of our app, by setting up an Express server with two POST routes to handle both register and login actions.
/* filename: ./index.js */
const Express = require("express");
const bcrypt = require("bcrypt");
// Import our User Model
const User = require("./model");
// Connection to Mongo DB
const Connect = require("./Connectdb");
const app = Express();
// CONSTANTS (these can be put in a .env file)
const SALT_ROUNDS = 10
const PORT = 3000
// Middleware for sending JSON type messages
app.use(express.json());
// Handling Registration for New Users
app.post('/register',(req, res)=>{
// CODE for handling Registration will go here ...
})
// Handling Login
app.post('/login',(req, res)=>{
// CODE for handling Login will go here ...
})
// Server Launch
app.listen(PORT, () => {
console.log(`Sever online and accessible via localhost:${PORT}.`);
});
Note: for more information on how to use the .env utility, see this article.
Step ONE
For this step we need to make two things. One, is to write the code that will enable the connection to our Mongo DB.
const mongoose = require("mongoose");
const ConnectDb = (url) => {
return mongoose.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
};
module.exports = ConnectDb;
Two, we create a user model. We keep it simple; our model will only have email, password, and username fields.
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
username: String,
password: String,
email: String,
});
module.exports = mongoose.model("User", UserSchema);
Step TWO
Now that everything is ready, it is time to hash password and add the salt!
To make the register controller, we need to:
- - Verify if the user already exists.
- - Generate a salt then, hash the password.
- - Save the user with the hashed password.
- - Return the
User
object.
//Register Route
app.post("/register", async (req, res) => {
// deconstruct the body sent via request
const { username, password, email } = req.body;
// check if all the information is filled properly
if (!username || !password || !email) {
res.status(400).json({ msg: "Please provide valid information." });
}
// Generate the salt
const salt = await bcrypt.genSalt(SALT_ROUNDS);
// Hash The password
const passwordHash = await bcrypt.hash(password, salt);
// Check if the user already exits in our Database
const IsExist = await User.findOne({ email });
if (IsExist) {
res.status(400).json({ msg: "User already exists." });
}
try {
const savedUser = await User.create({
username,
email,
password: passwordHash,
});
res.status(200).json({ msg: "Successfully added user:", savedUser });
} catch (error) {
res.status(500).json({ error });
}
});
To test this route, we can use _VS Code'_s extension, Thunder Client. We make an API request to our server with email and username and password in the body like so,
and as you can see, the response contains the hash of our password. It is important to note that the returned hash embeds information about its computation.
$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
| | | |
| | | hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
| | |
| | salt = nOUIs5kJ7naTuTFkBy1veu
| |
| cost-factor => 10 = 2^10 rounds
|
hash-algorithm identifier => 2b = BCrypt
Taken from the package's official npm page.
Step THREE
The elaboration of the login controller is much more concise than the registration process. All we need to do is,
- - Check if the user is actually registered.
- - Verify the password.
- - Return the
User
object.
// Login Route
app.post("/login", async (req, res) => {
// deconstruct the request body
const { email, password } = req.body;
// check if all the information is filled properly
if (!password || !email) {
res.status(400).json({ msg: "Please provide valid information." });
}
// check if user already exists
const userExists = await User.findOne({ email });
console.log(userExists);
if (!userExists) {
res.status(400).json({ msg: "Please Register." });
}
// verify the given password
const isPassword = await bcrypt.compare(password, userExists.password);
// if incorrect
if (!isPassword) {
res.status(400).json({ msg: "Email or password incorect." });
}
//if correct
res.status(200).json({ userExists });
});
We again use Thunder to test the route.
The response object do contain the user in our database and since the password is correct, the hash will match and the user can be logged safely without the need of storing sensitive data.
Please acknowledge that this is not a production-ready application. We wrote this article with the aim of showcasing the use of Bcrypt.js in authentication.
If you are a developer who doesn’t want to the headaches of cryptographic technicalities and just wants a default go-to utility. Bcrypt.js is what you need for all your hash matters. Don’t get me wrong, I am saying it is perfect in all regards but at least it mitigates the most obvious attacks.
To back up this claim. We share with you a benchmark test done with our potato PC. It shows computation-time cost per number of rounds.
As you can see, the number of rounds controls how much time is needed for computation. And for data transmission, any choice under 10 rounds wouldn’t put too much strain on the speed of your communications. The implementation wouldn't be too far from the register/login example.
Overall Bcrypt.js is simple and versatile. Let us know in the comment what do you think!
Find the code at CoddingAddicts/BcryptDoc
This was the Codding Addicts and until next time.
Posted on February 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.