DEV Community

loading...
Cover image for Understanding And Implementing Password Hashing In NodeJS.

Understanding And Implementing Password Hashing In NodeJS.

aditya278 profile image Aditya Shukla Updated on ・6 min read

Alt Text

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,

Hashing

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Bruteforce

  • 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.

Rainbow

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.

Salting

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
Enter fullscreen mode Exit fullscreen mode
  • Step 1: Import the module
const bcrypt = require('bcrypt');
const saltRounds = 10;
const plainText = "Hello World";
Enter fullscreen mode Exit fullscreen mode
  • 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.
  });
Enter fullscreen mode Exit fullscreen mode
  • 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.
 });
Enter fullscreen mode Exit fullscreen mode
  • 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);
      });
 });
Enter fullscreen mode Exit fullscreen mode

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);
 });
Enter fullscreen mode Exit fullscreen mode
  • 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.
 });
Enter fullscreen mode Exit fullscreen mode

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 through 2^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.

A Cat GIF for no reason at all

Discussion (3)

Collapse
tojacob profile image
Jacob Samuel G.

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 😅

Collapse
aditya278 profile image
Aditya Shukla Author

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.

Collapse
tojacob profile image
Jacob Samuel G.

Excellent article, by the way. Many of my doubts were cleared up. 😁⭐

Forem Open with the Forem app