Post

NodeJS Crypto Module

In this post we’ll look at how to use some features of the crypto library built into NodeJS. We’ll look at how to do symmetric encryption (AES), hashing strings and files, and finally ECDH (Elliptic-Curve Diffie-Hellman).

  1. Symmetric Encryption
  2. Hash Functions
  3. HMAC Generation
  4. Elliptic-Curve Diffie-Hellman

Symmetric Encryption

For a quick refresher, Symmetric Encryption is when only a single key is used. The same key is used for both encryption and decryption of the data.

Supported Ciphers

We can see which ciphers are supported in NodeJS by using the following code:

1
2
3
const crypto = require('crypto');  
  
console.log(crypto.getCiphers());  

crypto.getCiphers() returns a list of strings of all the ciphers that are supported. From the list we’ll choose aes-256-ctr. Let’s now see how to encrypt & decrypt some data using our chosen mode.

Encrypt String with AES-128-ECB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const crypto = require('crypto');

let key = Buffer.from('ABCDEFGHIJKLMNOP', 'utf-8');

// The Secret Message
let secret_msg = Buffer.from('To be Encrypted!', 'utf-8');

// Encrypt
let cipher = crypto.createCipheriv('aes-128-ecb', key, null);
let encryptedData = Buffer.concat([cipher.update(secret_msg), cipher.final()]);

// Decrypt
let decipher = crypto.createDecipheriv('aes-128-ecb', key, null);
let decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);

console.log(`\nEncryption Key: ${key.toString('hex')}`)

console.log(`Plain-text: \t"${secret_msg}"`);
console.log(`Plain-text: \t${secret_msg.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('base64')}`);
console.log(`Decrypted: \t${decryptedData.toString('hex')}`);
console.log(`Decrypted: \t"${decryptedData.toString('utf-8')}"`);
  • This is just an example, please don’t ever use ECB in practice.

Encrypt String with AES-256-CBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const crypto = require('crypto');

let key = Buffer.from('ABCDEFGHIJKLMNOPabcdefghijklmnop', 'utf-8');
let iv = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex');

// The Secret Message
let secret_msg = Buffer.from('To be Encrypted!', 'utf-8');

// Encrypt
let cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encryptedData = Buffer.concat([cipher.update(secret_msg), cipher.final()]);

// Decrypt
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decryptedData = Buffer.concat([decipher.update(encryptedData), decipher.final()]);

console.log(`\nEncryption Key: ${key.toString('hex')}`)
console.log(`IV: \t\t${iv.toString('hex')}\n`)

console.log(`Plain-text: \t"${secret_msg}"`);
console.log(`Plain-text: \t${secret_msg.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('base64')}`);
console.log(`Decrypted: \t${decryptedData.toString('hex')}`);
console.log(`Decrypted: \t"${decryptedData.toString('utf-8')}"`);

Encrypt String with AES-256-GCM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const crypto = require('crypto');

let key = Buffer.from('ABCDEFGHIJKLMNOPabcdefghijklmnop', 'utf-8');
let iv = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex');
let authTagLength = 16;

// The Secret Message
let secret_msg = Buffer.from('To be Encrypted!', 'utf-8');

// Encrypt
let cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength });
let encryptedData = Buffer.concat([cipher.update(secret_msg), cipher.final(), cipher.getAuthTag()]);

// Separate the encrypted data from the Auth Tag
let dataToDecrypt = encryptedData.slice(0, encryptedData.length - authTagLength);
let authTag = encryptedData.slice(encryptedData.length - authTagLength, encryptedData.length);

// Decrypt
let decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, { authTagLength });
decipher.setAuthTag(authTag);
let decryptedData = Buffer.concat([decipher.update(dataToDecrypt), decipher.final()]);

console.log(`\nEncryption Key: ${key.toString('hex')}`)
console.log(`IV: \t\t${iv.toString('hex')}\n`)

console.log(`Plain-text: \t"${secret_msg}"`);
console.log(`Plain-text: \t${secret_msg.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('hex')}`);
console.log(`Encrypted: \t${encryptedData.toString('base64')}`);
console.log(`Decrypted: \t${decryptedData.toString('hex')}`);
console.log(`Decrypted: \t"${decryptedData.toString('utf-8')}"`);

Note, the cipher and decipher objects created will automatically use PKCS padding. This can be disabled with the setAutoPadding function if needed.


Hash Functions

List Supported Hash Functions

We can see which hash functions are supported in NodeJS by using the following code:

1
2
3
const crypto = require('crypto');

console.log(crypto.getHashes());

crypto.getHashes() returns a list of strings of all the hash functions that are supported.

Hashing a String Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const crypto = require('crypto');

// String to be hashed
let msgToHash = 'Password1';

// Create Hash Functions
let MD4_HashFunction = crypto.createHash('md4');
let NTLM_HashFunction = crypto.createHash('md4');
let SHA256_HashFunction = crypto.createHash('sha256');
let SHA3_256_HashFunction = crypto.createHash('sha3-256');

// Add data to the hash function
MD4_HashFunction.update(msgToHash);
NTLM_HashFunction.update(Buffer.from(msgToHash, 'utf16le'));
SHA256_HashFunction.update(msgToHash);
SHA3_256_HashFunction.update(msgToHash);

// Get the result
console.log('MD4:\t\t', MD4_HashFunction.digest('hex'));
console.log('NTLM:\t\t', NTLM_HashFunction.digest('hex'));
console.log('SHA256:\t\t', SHA256_HashFunction.digest('hex'));
console.log('SHA3-256:\t', SHA3_256_HashFunction.digest('hex'));

The update function can be called multiple times before getting the output hash. For example:

1
2
SHA256_HashFunction.update("Pass");
SHA256_HashFunction.update("word1");

is the same as:

1
SHA256_HashFunction.update("Password1");

Hashing a File Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// Target file that we what to calculate the hash of
const targetFile = path.join(__dirname, 'input-file.txt');

// Create Hash Function
const SHA256_HashFunction = crypto.createHash('sha256');
SHA256_HashFunction.setEncoding('hex');

// Create a read stream with the target file
const inputFileStream = fs.createReadStream(targetFile);

// Callback for when Hashing is complete
inputFileStream.on('end', () => {
    SHA256_HashFunction.end();
    console.log(SHA256_HashFunction.read());
});

// Read the file contents and pipe it into the hash object
inputFileStream.pipe(SHA256_HashFunction);


HMAC Generation

Ideally a NIST approved hash function should be used:

  • SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, & SHA-512/256
  • SHA3-224, SHA3-256, SHA3-384, & SHA3-512
1
2
3
4
5
6
7
8
9
10
const crypto = require('crypto');

// Create the HMAC Function with the desired hash function and key.
const hmac = crypto.createHmac('sha3-256', 'MySecretKey');

// Add data to the HMAC
hmac.update('My data');

// Get the result
console.log('HMAC:', hmac.digest('hex'));


Elliptic-Curve Diffie-Hellman

Supported Curves

We can see which curves are supported in NodeJS by using the following code:

1
2
3
const crypto = require('crypto');

console.log(crypto.getCurves());

crypto.getCurves() returns a list of strings of all the elliptic curves that are supported. We’ll use the sect571k1 curve in the below example.

ECDH Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const crypto = require('crypto');

const alice = crypto.createECDH('sect571k1');
alice.generateKeys();

const bob = crypto.createECDH('sect571k1');
bob.generateKeys();

// Alice's Data
console.log("\nAlice Public:", alice.getPublicKey().toString('base64'));
console.log("Alice Private:", alice.getPrivateKey().toString('base64'), "\n");

// Bob's Data
console.log("Bob Public:", bob.getPublicKey().toString('base64'));
console.log("Bob Private:", bob.getPrivateKey().toString('base64'), "\n");

// The Shared Secret will be the same
console.log("Shared Secret: ", alice.computeSecret(bob.getPublicKey(), null, 'base64'));
console.log("Shared Secret: ", bob.computeSecret(alice.getPublicKey(), null, 'base64'), "\n");
This post is licensed under CC BY 4.0 by the author.