It is the user's responsibility to have a strong password, but as a developer, it is our responsibility to keep their passwords secured. We've learned it in Cybersecurity 101 - "Never, for the love of god, ever save the password in the form of plain text in your database. Always Hash it before saving". And it makes sense right? Password is a very, if not the most, sensitive and confidential information that a user is trusting the application with. It is possible that they might have the same password on any other platform, maybe even on any banking websites. It would be a disaster if someone else gets hold of the user's password, that too in plain text.
What exactly is Password Hashing?
Hashing basically means to transform the data into a string of fixed length using a hash function. This transformation is irreversible. The process is simple,
It is similar to Encryption, but with one difference i.e. the encrypted text can be decrypted using the Cryptographic keys, but it is nearly impossible to get the original string back by just having the hashed string. You can also call it as "One Way Encryption" just to remember it easily.
Click here to read my article on Medium where I have explained the difference between Encoding, Encryption and Hashing in detail.
Let's Implement a small program to understand it better.
const crypto = require('crypto')
const md5sum = crypto.createHash('md5');
let str = "Hello";
const res = md5sum.update(str).digest('hex');
console.log(res);
//8b1a9953c4611296a827abf8c47804d7
In the above code snippet, we have hashed a string using the md5 hashing algorithm in JavaScript.
Please note that, the hashed string that we are getting as the output is unique to the input string "Hello", it is nearly impossible to find any other string producing the same hash.
Just having just a hash only lets you verify the authenticity of the data. What I mean is, if you have the hash function and you pass the plain text through it, you'll get the hash value, and once you compare the generated hash value with what was stored in your database, then only the authenticity of that data can be validated.
But we need more than just that. We also need to verify the originator of the data along with it's authenticity i.e. we want to make sure that the entity passing the plain text to generate the hash for comparison is the same who originally generated it. This way we can make sure that it is the intended user only and not any unknown attacker. For that we use another technique called HMAC (Hash-based Message Authentication Code). This adds an extra layer of security for storing the password.
HMAC uses a Secret key. This secret key is used to create the hash, such that if any other key is used in the same hash function for the same text, the generated hash will be different. This ensures that the hash is being generated by a genuine entity.
Let's look at the code to understand better:
const crypto = require('crypto');
const hashingSecret = "ARandomSecretKey";
const plainText = "Hello World!";
const hashedStr = crypto.createHmac('sha256', hashingSecret)
.update(plainText)
.digest('hex');
consdole.log(hashedStr);
//a08116905e92633e4f30e53fq276206b25930rta383642fc5b7f51c089187939
Now, if we want to regenerate the same hash for the plainText = "Hello World!"
, then we would require the same secret key i.e. hashingSecret = "ARadnomSecretKey"
.
Okay, if I hash a password before storing it inside our database, so even if the hacker/attacker gets hold of the hashed password, I need not to worry. Right?
I wish this was true. Attackers can use Brute-Force, Dictionary or Rainbow Table attack to crack the hashed strings.
FYI
- Brute Force attack, works by calculating the hash function of every string present with them, calculating their hash value and then compare it with the one in the computer, at every step.
Dictionary attack is the simplest form of attack possible on a hash function. We simply store for each possible input the corresponding hash. Then, given a hash, we can look it up in our database, and find the matching input.
A Rainbow Table works by doing a cryptanalysis very quickly and effectively. Unlike bruteforce attack, a rainbow table attack already computes the hashes of the large set of available strings and stores it in a rainbow table.
Is there any way to avoid this? Answer is Yes there is, but it's a bit salty.
What is Salting?
No, it does not mean adding salt to you hash browns. Actually, Salting is a technique used in the hash generation process, to make the generated hash more secure. In salting, random data is added to the input string, then the combined string is passed through the hash function to get a unique hash. This generated hash will be different even for the same set of inputs.
And as you can guess, by doing so we are making the process of the attacks like Dictionary and Brute-force, very very slow as all the generated hashes are unique.
Let's understand it using code. In the code below, we'll be using a npm module called bcrypt. Since the methods are Asynchronous, we'll be using the promise approach to understand the different methods.
Follow the below steps before writing the code:
:~$ mkdir hashing
:~$ cd hashing
:~/hashing$ npm install bcrypt
- Step 1: Import the module
const bcrypt = require('bcrypt');
const saltRounds = 10;
const plainText = "Hello World";
- Step 2: Generate the Salt using
bcrypt.genSalt
method
bcrypt.genSalt(saltRounds)
.then(salt => {
console.log(salt);
//Output: $2b$10$uuIKmW3Pvme9tH8qOn/H7u
//A 29 characters long random string.
});
- Step 3: Inside the genSalt callback, call
bcrypt.hash
method
bcrypt.hash(plainText , salt)
.then(hash => {
console.log(hash);
//Output: $2b$10$uuIKmW3Pvme9tH8qOn/H7uZqlv9ENS7zlIbkMvCSDIv7aup3WNH9W
//A 61 character long string (notice that the 1st part of the string is actually the salt.
});
- So, if I combine the above code snippets into one, it'll be as below:
const bcrypt = require('bcrypt');
const saltRounds = 10;
const plainText = "Hello World";
bcrypt.genSalt(saltRounds)
.then(salt => {
bcrypt.hash(myPlaintextPassword, salt)
.then(hash => {
console.log(hash);
});
});
There is another way to generate the hash using bcrypt.hash
function. In this approach the salt is auto generated and we just need to pass the saltRounds
directly to the bcrypt.hash
function.
bcrypt.hash(myPlaintextPassword, saltRounds)
.then(hash => {
console.log(hash);
});
- Okay, so a Salted Hash is generated and stored in the DB during sign-up. Now when a user wants to login, we need to match the password entered by the user, with what is stored in the DB.
bcrypt module provides another method called
bcrypt.compare
to compare the input string with the hashed string.
// Load hash from your database for the password.
bcrypt.compare(loginPasswordString, hash)
.then(result => {
console.log(result);
// This will be either true or false, based on if the string
// matches or not.
});
Note: When we are hashing the data using bcrypt, the module will go through a series of rounds. This is done to return a secure hash. The value that you provide as the
saltRounds
, bcrypt will go through2^saltRounds
iterations. This makes the algorithm slower than any other hashing algorithm, and hence making it more secure as it also increase the time that an attacker has wait for cracking the hashed using brute force, dictionary or rainbow table attacks (sometimes even more than a year).
Read more about bcrypt module here.
In Conculsion:
- Passwords must always be stored as Hash Value in the database and not in Plain Text.
- We can use crypto module in NodeJS to implement different Hashing Algorithms like md5, sha264, hmac etc.
- Salting is a process in which a random string is added to the plain text before it is passed to the hash function to get a more secure hashed string.
- In my opinion, we must always use bcrypt module in NodeJS, as it allows Salting the hash, which makes it more secure and much more difficult for an attacker to crack it.
Follow me on Twitter, GitHub, and Medium for more content or just visit to say Hi.
Top comments (5)
Thank you so much for this article, it helped me a lot in a project, I loved your way of explaining, very didactic.
I'm not much into this but I heard that PBKDF2 is more suitable for hashing passwords than SHA-2, still I think salted SHA-2 should be currently unbreakable. Am I wrong?
If you do a post talking about different ways to secure passwords with the different algorithms mention me please, I'm interested on expanding my knowledge on that :)
Thanks!
How can Bcrypt know if the password is the same? π€ I mean, you can't really know what the random text was. Crypto issues always blow my mind π
It's not exactly that random. The hashed string generated after salting contains the salt itself. If you look at the hashed string closely, you'll see that the string is delimited by 3 $'s.
$2b$10$uuIKmW3Pvme9tH8qOn/H7uZqlv9ENS7zlIbkMvCSDIv7aup3WNH9W
$2b -> bcrypt version
$10 -> salt rounds
First 22 remaining characters (uuIKmW3Pvme9tH8qOn/H7u) -> generated salt.
So now using these information, when we do
bcrypt.compare
and pass the hashed string and plain text, bcrypt hashes the plain text again using the salt above, and then compare if it is same with the hashed string passed to the compare function.I hope that cleared your doubt.
Excellent article, by the way. Many of my doubts were cleared up. πβ