Ukpai Ugochi I am a female Nigerian with a Bachelor's degree in Marine engineering and Bootcamp certificates in Software development. I'm a full stack JavaScript developer (MEVN) stack. I love to share knowledge about my transition from marine engineering to software development in the form of writing, to encourage people who love software development and don't know where to begin. I also contribute to FOSS in my free time.

Node.js crypto module: A tutorial

10 min read 2867

Node.js crypto module: A tutorial

What would happen to user data if criminals were to get ahold of your database? Cybercrime is a persistent threat, and bad actors lurk at every corner seeking to pass malicious scripts to clone your database. What extra steps can you take to protect user information?

For instance, when a user creates an account in an application, their passwords and usernames need to be kept securely in the database, possibly by encrypting. However, to login into their accounts, the user’s password and username are verified against sets of credentials that are already in the database.

This cannot work if the passwords in the database — which are encrypted into gibberish — are used to compare the password/email the user inputs. That’s why the credentials in the database have to be decrypted while comparing them to the user’s input. Passwords can either be hashed or encrypted; hashing is a one-way encryption method.

The best solution is to employ cryptography on sensitive information before sending it to the database. This way, when cybercriminals get hold of your database, all they see are random characters.

In this tutorial, we’ll go over the basics of cryptography in Node.js and demonstrate how to use the Node.js crypto module to secure user data. We’ll build a sample app to demonstrate how to encrypt and decrypt usernames and passwords in Node.js using crypto.

Here’s what we’ll cover:

To follow along with this tutorial, you should have:

  • Node.js installed in your working environment
  • Git to download and set up git in your working environment
  • MongoDB to store user details

What is cryptography in Node.js?

Cryptography is the process of converting plain text into unreadable text and vice-versa. This way, only the sender and receiver of the information understand its content.

With cryptography in Node.js, you can hash passwords and store them in the database so that data cannot be converted to plain text after it is hashed; it can only be verified. When malicious actors get ahold of your database, they cannot decode the encrypted information. You, can also encrypt other user data so that it can be decrypted during transmission.

The kind of encryption you employ on your application depends on your needs. For instance, cryptography can be symmetric-key (such as hashing), public-key (such as encrypting or decrypting), and so on.

We made a custom demo for .
No really. Click here to check it out.

An end party that receives encrypted data can decrypt it to plain text for their consumption. Cybercriminals cannot decrypt encrypted data if they do not have the key. This is exactly what the Node.js crypto module does.

What is the Node.js crypto module?

The Node.js crypto module provides cryptographic functions to help you secure your Node.js app. It includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign, and verify functions.

crypto is built into Node.js, so it doesn’t require rigorous implementation process and configurations. Unlike other modules, you don’t need to install Crypto before you use it in your Node.js application.

crypto allows you to hash plain texts before storing them in the database. For this, you have a hash class that can create fixed length, deterministic, collision-resistant, and unidirectional hashes. For hashed data, a password cannot be decrypted with a predetermined key, unlike encrypted data. An HMAC class is responsible for Hash-based Message Authentication Code, which hashes both key and values to create a single final hash.

You may need toencrypt and decrypt other user data later for transmission purposes. This is where the Cipher and Decipher classes come in. You can encrypt data with the Cipher class and decrypt it with the Decipher class. Sometimes, you may not want to encrypt data before storing them in the database.

You can also verify encrypted or hashed passwords to ensure they are valid. All you need is the Verify class. Certificates can also be signed with thesign class.

All these are reasons developers love to use the crypto module. Let’s explore the various crypto classes and discover how to implement cryptography with them.

Node.js crypto classes

Let us look at the classes in crypto that enable us to implement cryptography.

Cipher

The Cipher class is responsible for encrypting information. When the user inputs a password during registration, the C``ipher class is called to encrypt the password.

First, we’ll generate a key from an algorithm. After that, we’ll generate a random initialization number (iv) before encrypting the text.

To use this class, you have to create a cipher instance using either the crypto.createCipher() or crypto.createCipheriv(). It‘s advised to use crypto.createCipheriv() since crypto.createCipher() is depreciated.

The program below shows how to encrypt passwords with the Cipher method:

// Import module into your application
const crypto = require('crypto');

const algorithm = 'aes-192-cbc';
const password = '2001MyForever';

// We will first generate the key, as it is dependent on the algorithm.
// In this case for aes192, the key is 24 bytes (192 bits).
crypto.scrypt(password, 'salt', 24, (err, key) => {
  if (err) throw err;
  // After that, we will generate a random iv (initialization vector)
  crypto.randomFill(new Uint8Array(16), (err, iv) => {
    if (err) throw err;

    // Create Cipher with key and iv
    const cipher = crypto.createCipheriv(algorithm, key, iv);

    let encrypted = '';
    cipher.setEncoding('hex');

    cipher.on('data', (chunk) => encrypted += chunk);
    cipher.on('end', () => console.log(encrypted));// Prints encrypted data with key

    cipher.write('some clear text data');
    cipher.end();
  });
});

Decipher

The Decipher class is responsible for decrypting encrypted texts. When you intend to send information securely to another developer, you have to encrypt it. The only way the receiver of the information can read the information is to decrypt it. This is exactly what the Decipher class does.

You cannot create decipher objects directly with the new keyword. The crypto.createDecipher() or crypto.createDecipheriv() methods are used to create decipher instances.

crypto.createDecipher() is depreciated, so you should use the crypto.createdeCipheriv() method instead.

Here’s how to decipher encrypted text with Decipher:

// Import module into your application
const crypto = require('crypto');

const algorithm = 'aes-192-cbc';
const password = 'Password used to generate key';

// We will first generate the key, as it is dependent on the algorithm.
// In this case for aes192, the key is 24 bytes (192 bits).
// We will use the async `crypto.scrypt()` instead for deciphering.
const key = crypto.scryptSync(password, 'salt', 24);
// The IV is usually passed along with the ciphertext.
const iv = Buffer.alloc(16, 0); // Initialization vector.

// Create decipher with key and iv
const decipher = crypto.createDecipheriv(algorithm, key, iv);

let decrypted = '';
decipher.on('readable', () => {
  while (null !== (chunk = decipher.read())) {
    decrypted += chunk.toString('utf8');
  }
});
decipher.on('end', () => {
  console.log(decrypted);
  // Prints: some clear text data
});

// Encrypted with same algorithm, key and iv.
const encrypted =
  'e5f79c5915c02171eec6b212d5520d44480993d7d622a7c4c2da32f6efda0ffa';
decipher.write(encrypted, 'hex');
decipher.end();

Hash

The Hash class is used for plain text hashing purpose. Hashing simply converts plain text into hash functions. Hashed text cannot be converted back to its original version. You cannot create hash objects directly with the new keyword.

To create a hash instance, use the crypto.createHash() method, as shown in the example below:

// Import module into your application
const crypto = require('crypto');
// create hash algorithm 
const hash = crypto.createHash('sha256');

hash.on('readable', () => {
  // Only one element is going to be produced by the
  // hash stream.
  const data = hash.read();
  if (data) {
    console.log(data.toString('hex'));
    // Prints:
    //   6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50
  }
});

hash.write('some data to hash');
hash.end();

Certificate

A Certificate is made up of a key pair and other information that is used for encrypting electronic documents.

A certificate can produce a session key for the purpose of transmitting information securely over the internet. With the crypto Certificate class, you can work with Signed Public Key and Challenge (SPKAC) using OpenSSL’s SPKAC implementation.

Below is an example of how to use the Certificate class:

const { Certificate } = require('crypto');
const spkac = getSpkacSomehow();
const challenge = Certificate.exportChallenge(spkac);
console.log(challenge.toString('utf8'));
// Prints: the challenge as a UTF8 string

DiffieHellman

To successfully decipher a cryptograph — especially encrypted cryptographs — you need a key. A key is like a shared secret between the sending party and the receiving party. If keys are not kept securely, hackers can get ahold of them and cause havoc with user information.

Crypto’s DiffieHellman class utilizes Diffie-Hellman key exchanges. Diffie-Hellman key exchange is a method for securely passing cryptographic keys in public channels. This secures keys that are specifically for information senders and receivers.

To use the DiffieHellman method, follow the example shown below:

onst crypto = require('crypto');
const assert = require('assert');

// Generate Alice's keys...
const alice = crypto.createDiffieHellman(2048);
const aliceKey = alice.generateKeys();

// Generate Bob's keys...
const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
const bobKey = bob.generateKeys();

// Exchange and generate the secret...
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey);

// OK
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));

ECDH

Elliptic-curve Diffie–Hellman (ECDH) is used for establishing a shared public-private key pair with elliptic-curve.

The example below shows how to use the ECDH class in your Node.js application.

const crypto = require('crypto');
const assert = require('assert');

// Generate Alice's keys...
const alice = crypto.createECDH('secp521r1');
const aliceKey = alice.generateKeys();

// Generate Bob's keys...
const bob = crypto.createECDH('secp521r1');
const bobKey = bob.generateKeys();

// Exchange and generate the secret...
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey);

assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
// OK

HMAC

Hash-based message authentication code (HMAC) enbles you to provide digital signatures with the use of shared secret. Crypto’s HMAC class uses the HMAC method for digital signing.

const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'a secret');

hmac.on('readable', () => {
  // Only one element is going to be produced by the
  // hash stream.
  const data = hmac.read();
  if (data) {
    console.log(data.toString('hex'));
    // Prints:
    //   7fd04df92f636fd450bc841c9418e5825c17f33ad9c87c518115a45971f7f77e
  }
});

hmac.write('some data to hash');
hmac.end();

sign

The sign class is for generating signatures. For efficient cryptography, cryptographs need to be signed and later verified for authentication. This way, when the receiver gets a cryptograph, they can tell whether it is genuine by verifying the signature on it.

const crypto = require('crypto');

const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
  namedCurve: 'sect239k1'
});

// Create
const sign = crypto.createSign('SHA256');
sign.write('some data to sign');
sign.end();
const signature = sign.sign(privateKey, 'hex');

verify

If you have a hashed cryptograph, the only way to ascertain its value is with the verify method.

For instance, when you hash a user password and store it in your database during registration, you need to confirm the password/username entered by the user during login. Since we cannot decipher hashed passwords, the only way to confirm that the password/username combo is correct is with verify.

// Verify signed token from `sign` example above
const verify = crypto.createVerify('SHA256');
verify.write('some data to sign');
verify.end();
console.log(verify.verify(publicKey, signature, 'hex'));
// Prints: true 

Using crypto in a Node.js app

To demonstrate how to encrypt and decrypt user information in a Node.js app using crypto, we’ll use a sample Node.js app in which users register with a username and password and then use those credentials to log in.

Let’s say that when the user registers, their details are stored in plain text, giving hackers easy access to steal their data. We’ll show you how to solve this problem by adding crypto to the application.

This application uses Passport for user authentication — in our case, to compare the user password/email combination.

First, download the sample Node.js application and clone it with this command:

git clone https://github.com/hannydevelop/Crypto.git

After cloning the application, navigate to the location of the application on your system with your terminal.

cd crypto

Install dependencies and start the application by running the command below in your terminal:

# Install dependencies 
npm install
# Start application
node index.js

Navigate to http://localhost:3000/register in your web browser to view the application. If you register an account and use MongoDB Compass to view the database, you would see that the user password is in plain text.

Node.js crypto: Password in Plaintext

We want to avoid this. To convert a password to a cryptograph, we can add crypto to our application.

Adding crypto to a Node.js app

To add crypto to your Node.js application, follow the steps below.

  1. Add the crypto module and specify salt for all users:
    // Import module into the application
    const crypto = require('crypto')
    // Creating salt for all users
    let salt = 'f844b09ff50c'
    
  2. Add the following code to your register method:
    // Add this right above User.create(userData)
    
    // Hash user password and salt using 1000 iterations
    let hash = crypto.pbkdf2Sync(userData.password, salt,  
    1000, 64, `sha512`).toString(`hex`);
    userData.password = hash

    usercontroller.js should now look like this:

    //import all dependencies required
    const express = require('express');
    const cors = require('cors');
    const crypto = require('crypto')
    //set variable users as expressRouter
    var users = express.Router();
    //import user model
    var { User } = require('../models/User');
    //protect route with cors
    users.use(cors())
    // Creating salt for all users
    let salt = 'f844b09ff50c'
    // Handling user signup 
    users.post('/register', (req, res) => {
      const userData = {
        //values should be those in the user model important
        username : req.body.username, 
        password: req.body.password,
        first_name: req.body.first_name,
        last_name: req.body.last_name,
      }
      User.findOne({
        //ensure username is unique, i.e the username is not already in the database
        username: req.body.username
      })
        .then(user => {
          //if the username is unique 
          if (!user) {
            let hash = crypto.pbkdf2Sync(userData.password, salt,  
            1000, 64, `sha512`).toString(`hex`);
            userData.password = hash
            //if the username is unique go ahead and create userData after hashing password and salt
              User.create(userData)
                .then(user => {
                  //after successfully creating userData display registered message
                  res.redirect('/login')
                })
                .catch(err => {
                  //if an error occured while trying to create userData, go ahead and display the error
                  res.send('error:' + err)
                })
          } else {
            //if the username is not unique, display that username is already registered with an account
            res.json({ error: 'The username ' + req.body.username + ' is registered with an account' })
          }
        })
        .catch(err => {
          //display error if an error occured
          res.send('error:' + err)
        })
    })

    Adding Crypto to Node.js

  3. Add the following to your login method.
    //check to see if a username and password match like this is in the database
    username: req.body.username,
    password: crypto.pbkdf2Sync(req.body.password, salt,  
    1000, 64, `sha512`).toString(`hex`)

    users.post('/login') should now look like this:

    users.post('/login', (req, res) => {
          User.findOne({
            //check to see if a username and password match like this is in the database
            username: req.body.username,
            password: crypto.pbkdf2Sync(req.body.password, salt,  
              1000, 64, `sha512`).toString(`hex`)
          })
            .then(user => {
              //if the username and password match exist in database then the user exists
              if (user) {
                const payload = {
                  username: user.username,  
                  password: user.password 
                }
                //after successful login display token and payload data
                res.redirect('/');
              } else {
                //if user cannot be found, display the message below
                res.json({ error: 'user not found' })
              }
            })
            //catch and display any error that occurs while trying to login user
            .catch(err => {
              res.send('error:' + err)
            })
      })

    crypto Node.js Login Method

Now that we’ve added crypto’s hash method to our Node.js application, let’s run it and see the difference.

To run your Node.js application, run the command below in your terminal:

node index.js

Navigate to http://localhost:3000/register to register and log into your application. This time, the snippet from Compass is different (it’s a cryptograph):

Node.js crypto in MongoDB Compass

Should you use Node.js crypto?

In this article, we demonstrated how to secure user data with the Node.js crypto module.

There are some other cryptography packages you can use Node.js, such as JWT, Bcrypt, and more. However, these packages are not built-in and sometimes require additional dependencies to do the job crypto can do on its own. For instance, if using Bcrypt, you would need to sign your keys with JWT.

crypto has a crypt and sign method that performs the same jobs as Bcrypt and JWT, respectively. However, if the application you’re building is solely for authenticating users and not ciphering messages, Bcrypt and JWT are better options. That’s because it’s not a good practice to use the same salt for all users; rather. you should create a unique salt for each user is a good practice this.salt = crypto.randomBytes(16).toString('hex');.

This process comes with its own challenge: the ability to compare a hashed password with a user-entered password. Bcrypt’s compareSync() method provides an easy means to compare hashed passwords and plain passwords and is, therefore, a better alternative.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Ukpai Ugochi I am a female Nigerian with a Bachelor's degree in Marine engineering and Bootcamp certificates in Software development. I'm a full stack JavaScript developer (MEVN) stack. I love to share knowledge about my transition from marine engineering to software development in the form of writing, to encourage people who love software development and don't know where to begin. I also contribute to FOSS in my free time.

2 Replies to “Node.js crypto module: A tutorial”

  1. Would suggest changing your aes example to use aes-256 and the password example mention why a higher iteration count is important, and may want to switch to the async methods, especially for higher iterations and mention countermeasures as this can be a point for DDoS depending on configuration and implementation details..

  2. Hello!
    Thank you for the kind feedback. Although larger key sizes exist mostly to satisfy some US military regulations which require several distinct security levels, the larger key sizes imply some CPU overhead (+20% for a 192-bit key, +40% for a 256-bit key. This is why most applications use 192-bit key. Also, the reason why most people will use a higher iteration is to make it difficult for attackers to easily decipher passwords. Sure! It’ll be a better idea to apply asynchronous programming for higher iteration, otherwise synchronous method as applied here presents no delay.

    Do well to reach out if you have further questions or suggestions. Thank you!

Leave a Reply