Beginner’s Guide to Data Encryption with NodeJS

Web development can’t survive without encryption.
Feature Image

As web apps continue to gain access to large amounts of sensitive data that belongs to people, organizations, and even governments, the threat to data security is also at an all-time high. Since the early days of programming, programmers used cryptography and encryption techniques to protect such sensitive data against malicious parties. Especially after introducing the internet and the world wide web, cryptography techniques play a crucial role in guaranteeing data security.

In web development, cryptography is often used to secure data while being transferred over a network or stored in databases. Most of the cryptographic operations are executed in a web backend. That’s why, as a Node developer, you should understand how to encrypt and decrypt data to secure the data processed by your system.

This post will go through basic cryptographic techniques used to secure data in web development and see how to implement them with Node.js. Node provides a built-in module called crypto to implement these tasks easily.

As the first step, we must understand what exactly cryptography (and encryption) is and how it works.


What is cryptography?

Cryptography is not one thing. It’s a whole area of study that deals with techniques to keep data secure. To achieve this goal, it primarily converts an initial set of data into something that anyone who doesn’t have the right credentials can neither understand nor convert back to its initial form. Some cryptographic techniques guarantee an outside party has not altered that data.

There are three important ingredients in a cryptographic process: plaintext, ciphertext, and an algorithm. The cryptographic algorithm converts data in plaintext to ciphertext, a scrambled and unrecognizable form of initial data. The algorithm uses mathematical calculations for this conversion.

Usually, cryptographic algorithms use something called a key when converting the plaintext to ciphertext. Converting the ciphertext back to plaintext is possible only if you have the right key with you. Therefore, those who have the privilege to view plaintext must store this key under maximum security and not let it fall into the hands of outsiders.


What is encryption?

While these terms, encryption and cryptography, are sometimes used interchangeably, they are not the same thing.

Encryption is the process of converting plaintext to ciphertext using an algorithm. It’s only one aspect of cryptography. The opposite of encryption, the process of converting the ciphertext back to plaintext, is called decryption.

In cryptography, we can find a number of encryption algorithms used for converting plaintext.


Types of cryptography

There are three basic types of cryptography: symmetric-key cryptography, asymmetric-key cryptography, and hashing. Let’s see how each one is important in data security.

Symmetric-key cryptography

In the previous section, we discussed how encryption algorithms use keys when converting plaintext to ciphertext and vice versa. In symmetric-key cryptography, the key used for converting plaintext to ciphertext and the key used for converting ciphertext back to plaintext are the same. This type of cryptography is simple to implement and faster than its counterpart, asymmetric-key cryptography.

But symmetric-key cryptography has one major security concern. When encrypting data transmitted over a network, this method requires both the sender and the receiver to have the key used for encryption in their possession. This creates a scenario where the encryption key could be compromised by a third-party.

When the sender and receiver try to exchange the encryption key over a network, a third party has a chance to steal the key. Especially when the said network is the internet, the risk of exchanging keys is high.

Asymmetric-key cryptography

Asymmetric-key cryptography was introduced to overcome the security concerns of symmetric-key cryptography. It uses a pair of keys, instead of one, called the public key and private key. The public key is only used for encrypting data. The private key is only used for decrypting data. You can’t use the public key to decrypt data encrypted with it or vice versa.

The specialty of this pair-key system is that even if the public key is shared among outside parties other than the receiver or the sender, it doesn’t compromise the system’s security. Since the public key is useless in decryption, stealing the public key doesn’t open the chance to read encrypted and private user data.

Hashing

Hashing is different from the above two types of cryptography.

Symmetrical and asymmetrical cryptography facilitates the conversion of plaintext to ciphertext and ciphertext back to plaintext. In hashing, however, you only have one option: converting the plaintext to ciphertext. Once the plaintext is “hashed”, you can’t convert it back to check its original content.

What you can do instead is checking if another plaintext generates the same hash value to see if the two plaintexts are equal. This is possible because hashing algorithms guarantee that every unique plaintext generates a unique ciphertext.

Hashing is often used when storing passwords. Applications never store the plaintext password in their databases. Instead, they store a hash of the password. When authenticating a user, we have to check if the hash generated by the password a user provides is equal to the one stored in the database. If the provided password’s hash is equal to the stored hash, then the system can authenticate the user.

Unlike symmetric and asymmetric cryptography, hashing doesn’t use a key during the hashing process.


Encryption with Node.js

Node.js has the built-in module, crypto, which provides functions to carry out cryptographic operations. It includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign, and verify functions.

In this section, we will see how to implement encryption using the crypto module. Before we begin, you have to set up your usual Node project environment and install the crypto module using npm.

Encrypting data with Node.js

We can use the Cipher class of the crypto module to encrypt data. So, let’s implement the encryption function.

const crypto = require("crypto");

const algorithm = "aes-192-cbc";

const encrypt = (text) => {
  //generate encryption key using the secret.
  crypto.scrypt(process.env.SECRET, 'salt', 24, (err, key) => {
    if (err) throw err;

    //create an initialization vector
    crypto.randomFill(new Uint8Array(16), (err, iv) => {
      if (err) throw err;

      const cipher = crypto.createCipheriv(algorithm, key, iv);

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

      cipher.on('data', (chunk) => encrypted += chunk);
      cipher.on('end', () => console.log(encrypted))
      cipher.on('error', (err) => console.log(err))

      cipher.write(text);
      cipher.end();
    });
  });
}

encrypt('hello World');

When encrypting with the crypto module, we have the option to generate a new key every time the encrypt method is called. Using different keys for encryption makes it harder for attackers to decipher data using brute force.

To generate a key, however, we use a common secret for both encryption and decryption. If the party that decrypts the data doesn’t have access to the secret that was used to generate the encryption key, the decryption process is going to fail.

The encryption algorithm we have used is the AES (Advanced Encryption Standard) 192 bits (24 bytes) algorithm. Here the 192 bits indicate the length of the key. You can see how we have passed the key length as 24 to the key generating scrypt function.

When creating a new cipher object, we pass a parameter called an initialization vector (IV). It is usually added to the ciphertext without going through an encryption process. It is used to avoid repetitions in the ciphertext so that attackers are not able to decipher data by identifying patterns in the encrypted data. Therefore, IVs should always be unpredictable and unique. For this reason, this implementation generates a new IV using the randomFill function.

Finally, data encryption is done using the write function. The cipher object triggers the “data” event whenever a chunk of data is available from the encrypted data stream and that chunk is then appended to the encrypted string. We can detect when encryption finishes by triggering the “end” event. We use another event, “error”, to detect errors thrown while encrypting the text.

Decrypting data with Node.js

To decrypt data, we use the Decipher class of the crypto module. It is implemented similar to the way data encryption was implemented. We create a new encryption key using the secret and then begin decrypting the cipher with the key.

const decrypt = (encrypted, iv) => {
  //generate encryption key using secret
  crypto.scrypt(process.env.SECRET, 'salt', 24, (err, key) => {
    if (err) throw err;

    //create decipher object
    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));
    decipher.on('error', (err) => console.log(err))

    decipher.write(encrypted, 'hex');
    decipher.end();
  })
}

To decrypt the ciphertext, you need to pass the IV used to encrypt the data in the encrypt function.

Hashing with Node.js

Implementing a hash function with crypto is quite simple. Here’s the hash function we have created.

const hash = text => {

  const hash = crypto.createHash('sha256');

  hash.on('readable', () => {

    const data = hash.read();
    if (data) {
      console.log(data.toString('hex'));
    }
  });

  hash.write(text);
  hash.end();
}

sha256 is the hash algorithm we have used in this implementation. Using this algorithm, it can easily create a hash text with the write function. When hashed data is available on the stream, it triggers the “readable” event and we can retrieve the hash string.


Summary

In today’s post, we discussed the basics of cryptography and data encryption. We also implemented cryptographic operations in Node.js with its built-in crypto module. Since data encryption is crucial to developing web applications, you can directly apply the things we learned today to build more secure and reliable applications in the future. If you want to read more about other types of cryptographic operations supported by Node’s crypto module, read Node’s official documentation here.

Cryptography is a vast and fascinating area of study. It offers more data protection methods than what we discussed in this post. The more you know about these methods the more they will benefit you as a web developer to build better applications. So don’t stop learning encryption and cryptography techniques from this post. Do your own research and learn more.

Programming JavaScript NodeJS API Development Web Development