This tutorial aims at teaching you how to encrypt and decrypt data in Node.js.
The method provided here is pretty straightforward and easy to understand, as it has been written with the intention of enabling other programmers and developers to learn how to encrypt data in their applications.
This encryption method can be used for files, messages, or any other data that your application needs to encrypt.
In this tutorial, the Encryption and Decryption methods provided are based on the AES-256 algorithm, and why we are using this because it is one of the most popular encryption algorithms in use today, and it’s well known for being secure.
NOTE: The AES-256 algorithm is a symmetric-key algorithm, meaning that the same key is used for encryption and decryption. This is in contrast to asymmetric-key algorithms, which use a public key for encryption and a private key for decryption.
Prerequisites
- Node.js
- NPM or Yarn
- Knowledge of Javascript
Getting Started
To get started, you need to create a new project folder and initialize npm or yarn in it. To do this, run the following commands in your terminal:
I'd be using yarn in this tutorial, but you can use npm if you prefer.
mkdir nodejs-encryption
cd nodejs-encryption
yarn init -y
This will create a new folder called nodejs-encryption and initialize yarn in it. The -y flag is used to skip the interactive mode and use the default values.
Installing Dependencies
To install the dependencies, run the following command in your terminal:
yarn add express dotenv
yarn add -D nodemon
This will install the express and dotenv modules and the nodemon module will be installed in the devDependencies.
Modifying the package.json file
We will be using the module type in the package.json file to enable the use of ES6 modules in our project also we will be adding the start and dev scripts to the scripts object in the package.json file.
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
Creating the Config File
To create the config file, create a new file called config.js in the root directory of your project. In this file, you will be storing the secret key, secret iv, and encryption method. The secret key and secret iv are used to generate the secret hash, which is used for encryption and decryption. The encryption method is used to specify the encryption algorithm to use. In this tutorial, we will be using the AES-256 algorithm.
// config.js
import dotenv from 'dotenv'
dotenv.config()
const { NODE_ENV, PORT, SECRET_KEY, SECRET_IV, ECNRYPTION_METHOD } = process.env
export default {
env: NODE_ENV,
port: PORT,
secret_key: SECRET_KEY,
secret_iv: SECRET_IV,
ecnryption_method: ECNRYPTION_METHOD,
}
NOTE: The config file is imported in the encryption.js file, so you need to ensure that the config file is in the same directory as the encryption.js file.
Creating the Server File
To create the server file, create a new file called server.js in the root directory of your project.
// server.js
import express from 'express'
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
This will create a new express server and listen on port 3000.
Creating the Encryption Module
To create the encryption module, create a new file called encryption.js in the root directory of your project.
In this file, you will be importing the crypto module and the config file. The config file contains secret_key, secret_iv, and ecnryption_method.
These variables are then assigned to their own variables. If any of these variables are missing, an error is thrown.
A secret hash is then generated with crypto to use for encryption. This hash is created using the sha512 method and the secret_key and secret_iv from the config file. The hash is then cut down to 32 characters for the key and 16 characters for the iv.
NOTE: iv stands for initialization vector. The initialization vector is a random value that is used to encrypt the first block of plaintext. The initialization vector is required to decrypt the ciphertext, so it must be transmitted or stored alongside the ciphertext.
Two functions are then exported: encryptData() and decryptData(). The encryptData() function takes in data as an argument and uses the cipheriv method from crypto to encrypt it, converting it to hexadecimal format before converting it to base64 format. The decryptData() function takes in encrypted data as an argument and converts it to utf8 format before using decipheriv from crypto to decrypt it, converting it back to utf8 format.
// encryption.js
import crypto from 'crypto'
import config from './config.js'
const { secret_key, secret_iv, ecnryption_method } = config
if (!secret_key || !secret_iv || !ecnryption_method) {
throw new Error('secretKey, secretIV, and ecnryptionMethod are required')
}
// Generate secret hash with crypto to use for encryption
const key = crypto
.createHash('sha512')
.update(secret_key)
.digest('hex')
.substring(0, 32)
const encryptionIV = crypto
.createHash('sha512')
.update(secret_iv)
.digest('hex')
.substring(0, 16)
// Encrypt data
export function encryptData(data) {
const cipher = crypto.createCipheriv(ecnryption_method, key, encryptionIV)
return Buffer.from(
cipher.update(data, 'utf8', 'hex') + cipher.final('hex')
).toString('base64') // Encrypts data and converts to hex and base64
}
// Decrypt data
export function decryptData(encryptedData) {
const buff = Buffer.from(encryptedData, 'base64')
const decipher = crypto.createDecipheriv(ecnryption_method, key, encryptionIV)
return (
decipher.update(buff.toString('utf8'), 'hex', 'utf8') +
decipher.final('utf8')
) // Decrypts data and converts to utf8
}
Creating the Routes
To create the routes, create a new file called routes.js in the root directory of your project.
In this file, you will be importing the express router and the encryption module. The encryption module contains the encryptData() and decryptData() functions.
The encryptData() function is used to encrypt the data sent in the request body and the decryptData() function is used to decrypt the data sent in the request body.
// routes.js
import express from 'express'
import { encryptData, decryptData } from './encryption.js'
const router = express.Router()
router.post('/encrypt', (req, res) => {
const { data } = req.body
const encryptedData = encryptData(data)
res.json({ encryptedData })
})
router.post('/decrypt', (req, res) => {
const { encryptedData } = req.body
const data = decryptData(encryptedData)
res.json({ data })
})
export default router
Creating the .env File
To create the .env file, create a new file called .env in the root directory of your project.
In this file, you will be storing the secret_key, secret_iv, and encryption_method. The secret_key and secret_iv are used to generate the secret hash, which is used for encryption and decryption. The encryption_method is used to specify the encryption algorithm to use.
In this tutorial, we will be using the AES-256 algorithm.
NODE_ENV=development
PORT=3000
SECRET_KEY=secretKey
SECRET_IV=secretIV
ECNRYPTION_METHOD=aes-256-cbc
NOTE: The .env file is imported in the config.js file, which is used to store the secret_key, secret_iv, and encryption_method.
Creating the .gitignore File
To create the .gitignore file, create a new file called .gitignore in the root directory of your project. In this file, you will be ignoring the node_modules folder and the .env file.
node_modules
.env
Importing the Routes in the Server File
To import the routes in the server file, import the routes file in the server.js file. Our updated server.js file will look like this:
// server.js
import express from 'express'
import routes from './routes.js'
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(routes)
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
Testing the API
To test the API, start the server by running the following command in the terminal:
yarn dev
NOTE: The dev script is defined in the package.json file.
To test the encrypt route, send a POST request to http://localhost:3000/encrypt with the following data in the request body:
{
"data": "Hello World"
}
To test the decrypt route, send a POST request to
http://localhost:3000/decrypt with the following data in the request body:
{
"data": "encryptedData"
}
Conclusion
In this tutorial, you learned how to create an API that encrypts and decrypts data using Node.js and Express. You also learned how to use the crypto module to encrypt and decrypt data.
You can find the source code for this tutorial on GitHub repo.
If you have any questions, feel free to ask them in the comments section below.
Happy coding!
Top comments (8)
This is a very sensitive topic to address. You have presented the article well but I'm afraid there is a problem with your example that defeats the point of the IV. If you read the definition of IV in your actual post:
NOTE: iv stands for initialization vector. The initialization vector is a random value that is used to encrypt the first block of plaintext. The initialization vector is required to decrypt the ciphertext, so it must be transmitted or stored alongside the ciphertext.
What this description may fail to impress is that an IV should be unique enough never to be used more than once with a single key. Your example essentially uses a static IV each and every time. This is bad practice and I don't recommend advising anyone to do this going forward.
What you should do is ensure the IV is suitably random (crypto-random) for each and every call and then store that IV as part of the generated cipher-text. It is perfectly OK for this to be done as you do not have to protect the IV from the cipher-text. This IV can then be used as part of the decryption process in subsequent calls.
There are more things you can do to harden up use of your key too. I recommend reviewing this very useful Gist as of writing: Node.js - AES Encryption/Decryption with AES-256-GCM using random Initialization Vector + Salt
Thanks for the article, but it would be good to update accordingly, at least around the use of the IV.
Thank you so much for pointing this out and shedding more light on IV, I'll make an adjustment and proceed with this practice henceforth.
Thanks for this article!! I have a question about this bit
If I understood correctly, this will generate the
key
andencryptionIV
values, which will be constants, so why not just saving those already calculated values in the .env file directly?It's not a good practice to save them in the env as every encryption hash is expected to have its own unique encryption key.
Here is how to do it right:
Thank you for reading my article. I try as much as possible to write beginner-friendly articles.
Thank you very much, I am learning nodejs and I have tried to implement this feature to my project. And this is the most comprehensive article I've ever read.