fbpx

Securing API Data: Encrypting and Decrypting with Node.js Middleware

  • Home
  • Blogs
  • Securing API Data: Encrypting and Decrypting with Node.js Middleware

By Muhammad Aqeel

In today’s digital landscape, securing sensitive data is not just a best practice but a necessity. Whether you’re handling user credentials, financial transactions, or other critical information, encrypting data before sending it over APIs ensures that even if intercepted, it remains unreadable to unauthorized parties. As applications interact with APIs to exchange data, ensuring this data remains confidential and integral is crucial.

In this article, we’ll dive into how to use encryption and decryption techniques in a Node.js backend. You’ll learn how to implement these methods effectively using middleware, ensuring your API data stays secure against potential threats.

The Role of Encryption and Decryption in Security

Vulnerability Assessment and Penetration Testing (VAPT) exercises frequently highlight the risk posed by unencrypted data transmissions. During these tests, ethical hackers simulate real-world attacks to identify and exploit vulnerabilities in systems. Unencrypted data transmissions represent low-hanging fruit for attackers, providing them with potentially sensitive information that can be leveraged for malicious purposes.

Encryption is the process of converting plain text into unreadable ciphertext using algorithms and keys, whereas decryption reverses this process, converting ciphertext back into plain text. This fundamental security measure not only protects sensitive information but also safeguards the integrity and confidentiality of data exchanged between clients and servers.

Benefits of Encrypting API Data

Encrypting API data offers several key benefits:

  • Confidentiality: Encrypted data remains confidential, protecting sensitive information from unauthorized access.
  • Integrity: Encryption ensures data integrity by detecting any unauthorized modifications during transmission.
  • Compliance: Adhering to data protection regulations mandates implementing encryption to safeguard user data.
  • Trust and Reputation: Prioritizing data security builds trust and credibility with users, enhancing your reputation and reducing the risk of data breaches.

Setting Up Your Node.js Project Environment

Before diving into encryption, ensure you have Node.js installed. You’ll also need npm or yarn for managing dependencies. Initialize a new Node.js project and install necessary packages such as crypto-JS and querystring for encryption and decryption operations.

npm init -y

npm install crypto-js querystring

Implementing Encryption Middleware

Middleware intercepts and processes incoming and outgoing API requests. Create middleware functions in Node.js to intercept outgoing API requests, encrypting sensitive data before transmission.

Import the necessary packages

const CryptoJS = require("crypto-js");

Define the Advanced Encryption Standard (AES) encryption function

async function _encryptCryptoJS(res, key) {
    const encryptedResponse = CryptoJS.AES.encrypt(JSON.stringify(res), key).toString();
    return encryptedResponse;
}

Here res is the response data we are going to pass from the API. The key is the secret used in the encryption process.

Handle the response while calling the encryption function

async function encryptResponse(req, res, next) {
    const key = "your_secret_key"; 
    const oldJson = res.json;
    res.json = async (body) => {
        let encryptedObject = '';
        if (body.data){
            encryptedObject = await _encryptCryptoJS(body.data, key);
        }
        return oldJson.call(res, {...body, data:encryptedObject ,dataEncrypted: 'true'});
    };
    next();
};

It is good practice to implement mechanisms for securely storing keys, such as environment variables. Then you can use the key in the middleware simply using “process.env.SECRET_KEY”. Regularly rotate keys to minimize security risks associated with compromised keys.

Export the middleware for the response encryption

'use strict'

const CryptoJS = require("crypto-js");

module.exports = () => {
    async function encryptResponse(req, res, next) {
        const key = process.env.SECRET_KEY;
        const oldJson = res.json;
        res.json = async (body) => {
            let encryptedObject = '';
            if (body.data){
                encryptedObject = await _encryptCryptoJS(body.data, key);
            }
            return oldJson.call(res, {...body, data:encryptedObject ,dataEncrypted: 'true'});
        };
        next();
    }

    async function _encryptCryptoJS(res, key) {
        const encryptedResponse = CryptoJS.AES.encrypt(JSON.stringify(res), key).toString();
        return encryptedResponse;
    }

return {encryptResponse: encryptResponse}
}

“use strict” is used to run the code in strict mode.

Implementing Decryption Middleware

Similarly, set up middleware as below to intercept incoming API requests, decrypting encrypted data back into its original form for further processing within your application.

Import the necessary packages

const CryptoJS = require("crypto-js");
const querystring = require('querystring');

If the encrypted values are sent as query parameters by using “encodeURIComponent” , the ‘parse’ method in ‘querystring’ will then correctly decode these percent-encoded UTF-8 characters.

Define the Advanced Encryption Standard (AES) decryption function

async function _decryptCryptoJS(encryptedParams, key) {
    const decryptedParams = CryptoJS.AES.decrypt(encryptedParams, key).toString(CryptoJS.enc.Utf8);
    return JSON.parse(decryptedParams);
}

Encrypted parameters are decrypted using the secret key used for encryption. “.toString(CryptoJS.enc.Utf8)” converts the decrypted data from a CryptoJS object to a UTF-8 string format, making it readable. JSON.parse is a built-in JavaScript method that converts the decrypted JSON-formatted string into a JavaScript object.

Handle the request while calling the decryption function

async function decryptRequest(req, res, next) {
    const key = "your_secret_key"; 
    if (req.query && req.query.encryptedParams) {
        const encryptedParams = req.query.encryptedParams
        const decryptedString = await _decryptCryptoJS(encryptedParams, key)
        req.query = querystring.parse(decryptedString)
    }
    else if (req.body && req.body.encryptedParams) {
        const encryptedParams = req.body.encryptedParams
        req.body = await _decryptCryptoJS(encryptedParams, key)
    }
    next();
}

Here we have assumed that the request parameters are passed in a variable called ‘encryptedParams’. The parameters are decrypted inside the if and else if statements considering how they are sent in the request.

Export the middleware for the request decryption

'use strict'

const CryptoJS = require("crypto-js");
const querystring = require('querystring');

module.exports = () => {
    async function decryptRequest(req, res, next) {
        const key = process.env.SECRET_KEY; 
        if (req.query && req.query.encryptedParams) {
            const encryptedParams = req.query.encryptedParams
            const decryptedString = await _decryptCryptoJS(encryptedParams, key)
            req.query = querystring.parse(decryptedString)
        }
        else if (req.body && req.body.encryptedParams) {
            const encryptedParams = req.body.encryptedParams
            req.body = await _decryptCryptoJS(encryptedParams, key)
        }
        next();
    }

    async function _decryptCryptoJS(encryptedParams, key) {
        const decryptedParams = CryptoJS.AES.decrypt(encryptedParams, key).toString(CryptoJS.enc.Utf8);
        return JSON.parse(decryptedParams);
    }

return {decryptRequest: decryptRequest}
}

Import middleware into your routes file

In your routes file, you can import these middleware and use them for the encryption and decryption of your API data.

'use strict'

const SampleController = require('./controllers/sample_controller')
const requestDecryptionMiddleware = require('./middleware/request_decryption_middleware')
const responseEncryptionMiddleware = require('./middleware/response_encrypt_middleware')


module.exports = (app) => {
    const sampleController= SampleController(app)

    app.get('/sampleDetails',requestDecryptionMiddleware().decryptRequest, responseEncryptionMiddleware().encryptResponse, sampleController.getSampleDetails)
    app.post('/createSample', requestDecryptionMiddleware().decryptRequest, responseEncryptionMiddleware().encryptResponse, sampleController.createSample)
}

Conclusion

Implementing encryption and decryption middleware in your Node.js backend enhances the security of your API data, protecting it from unauthorized access and ensuring compliance with data protection regulations. By following the steps outlined in this article, you’ve learned how to integrate robust encryption techniques seamlessly into your application workflow. Remember, securing sensitive data not only mitigates risks associated with data breaches but also demonstrates your commitment to protecting user privacy and complying with regulatory standards.