Cryptographic Practices

Let's make the first statement as strong as your cryptography should be - hashing and encrypting are two different things.

There's a general misconception and most of the time hashing and encrypting are used interchangeably, incorrectly. They are different concepts and they also serve different purposes.

Hashing

A hash is a string or number generated by a (hash) function from source data

hash = F(data)

The hash has fixed length and its value vary widely with small variations in input (collisions may still happen). A good hashing algorithm won't allow a hash to turn into its original source1. MD5 is the most popular hashing algorithm, however security-wise BLAKE2 is considered the strongest and most flexible. That being said, BLAKE2 has very little support in Node.js, but for demonstration purposes we will use the blakejs package. If for any reason we cannot use it, we fallback to SHA-256.

Example using blakejs

const blake = require('blakejs');

console.log(blake.blake2bHex('JS - Secure Coding Practices'));
// prints 0b72e001aa51f2f0ae9c7aca563596551bb8f1b3cbb48b5be509e998e71587152eaa62db361f5060f03a96a713588d60c164654659bb5993a5908b187646e063

console.log(blake.blake2sHex('JS - Secure Coding Practices'));
// prints bd3eb89e82ccfb45677b507aec93b5262768c879a60d2f158bf25a11d7fab07f

The blakejs package and its documentation is available here.

Example of SHA256 hashing using Node.js's crypto library:

const crypto = require('crypto');
const pass = "SecretPassword";

const hash = crypto.createHash('sha256').update(pass).digest('base64');

console.log(hash);
// Result: 1LyW5Lkjdw11Aeifiajm5Nh7th8dKnk53ncqd6IhpNs=

So remeber, when you have something that you don't need to know its content, only if it's what it is supposed to be (such as checking file integrity after download), you should use hashing2

Encryption

On the other hand, encryption turns data into variable length data using a key

encrypted_data = F(data, key)

Unlike the hash, we can compute data back from encrypted_data applying the right decryption function and key

data = F⁻¹(encrypted_data, key)

Encryption should be used whenever you need to communicate or store sensitive data, which you or someone else needs to access later for further processing. A 'simple' encryption use case is the HTTPS - Hyper Text Transfer Protocol Secure.

AES is the de facto standard when it comes to symmetric key encryption. This algorithm, as many other symmetric ciphers, can be implemented in different modes.

You'll notice in the code sample below, GCM (Galois Counter Mode) was used, instead of the more popular (in cryptography code examples, at least) CBC/ECB. The main difference between GCM and CBC/ECB is the fact that the former is an authenticated cipher mode, meaning that after the encryption stage, an authentication tag is added to the ciphertext, which will then be validated prior to message decryption, ensuring the message has not been tampered with.

The code sample of AES-256-GCM encryption and decryption using the crypto module:

const crypto = require('crypto');

// generate a new IV for each encryption
const iv = crypto.randomBytes(12)M

// generate salt
const salt = crypto.randomBytes(64);

// the masterkey - Example only!
const masterkey = "09Kssa22daVsF2jSV5brIHu1545Kjs82";

function encrypt (text) {
  // create the cipher
  const cipher = crypto.createCipheriv('aes-256-gcm', masterkey, iv);

  // encrypt the plaintext
  let encrypted = cipher.update(text, 'utf8', 'hex');

  // we are done writing data
  encrypted += cipher.final('hex');

  // get authentication tag (GCM)
  const tag = cipher.getAuthTag();

  return {
    content: encrypted,
    tag: tag
  };
}

function decrypt (encrypted) {
  // initialize deciphering
  const decipher = crypto.createDecipheriv('aes-256-gcm', masterkey, iv);

  // set our authentication tag
  decipher.setAuthTag(encrypted.tag);

  // update ciphertext content
  let dec = decipher.update(encrypted.content, 'hex', 'utf8');

  // finalize writing of data and set encoding
  dec += decipher.final('utf8');

  // return plaintext
  return dec;
}

let result = encrypt("JS - Secure Coding Practices");
console.log(result);
// b08fc897face59992f901acf0dc21c745640fe9989bf988318d15145

result = decrypt(result);
console.log(result);
// JS - Secure Coding Practices

There are also packages for this purpose. One of the most popular is node-forge and for completeness we will also show how an example using it. Note that node-forge consists of a native implementation of the TLS protocol, a set of cryptographic utilities, and a set of tools for developing web apps that require many network resources.

Let's see how the aes-256-cbc encryption/decryption can be achieved using node-forge:

Encryption

const forge = requires('node-forge');

// generate a random key and IV
// Note: a key size of 16 bytes will use AES-128, 24 => AES-192, 32 => AES-256
const key = forge.random.getBytesSync(16);
const iv = forge.random.getBytesSync(16);

// encrypt some bytes using GCM mode
const cipher = forge.cipher.createCipher('AES-GCM', key);
cipher.start({
  iv: iv, // should be a 12-byte binary-encoded string or byte buffer
  additionalData: 'binary-encoded string', // optional
  tagLength: 128 // optional, defaults to 128 bits
});
cipher.update(forge.util.createBuffer(someBytes));
cipher.finish();

const encrypted = cipher.output;
const tag = cipher.mode.tag;

// outputs encrypted hex
console.log(encrypted.toHex());

// outputs authentication tag
console.log(tag.toHex());

// continues below

Decryption

// continuation of the previous snippet

// decrypt some bytes using GCM mode
const decipher = forge.cipher.createDecipher('AES-GCM', key);
decipher.start({
  iv: iv,
  additionalData: 'binary-encoded string',  // optional
  tagLength: 128,                           // optional, defaults to 128 bits
  tag: tag                                  // authentication tag from encryption
});
decipher.update(encrypted);

const pass = decipher.finish();

// pass is false if there was a failure (e.g. authentication tag didn't match)
if (pass) {
  // outputs decrypted hex
  console.log(decipher.output.toHex());
}

On the other hand, you have public key cryptography or asymmetric cryptography which makes use of the following pairs of keys - public and private. Public key cryptography is less performant than symmetric key cryptography for most cases, so its most common use-case is sharing a symmetric key between two parties using asymmetric cryptography, so they can then use the symmetric key to exchange messages encrypted with symmetric cryptography.

Aside from AES, which is 90's technology, it supports more modern symmetric encryption algorithms which also provide authentication, such as chacha20poly1305.

Another interesting package in Node.js is sodium. This is a reference to Dr. Daniel J. Bernstein's NaCl library, which is a very popular modern cryptography library. It's essentially a port of the Libsodium encryption library to Node.js. The sodium package is comprised of implementations of NaCl's abstractions for sending encrypted messages for the two most common use-cases:

  • Sending authenticated, encrypted messages between two parties using public key cryptography
  • Sending authenticated, encrypted messages between two parties using symmetric (aka secret-key) cryptography

It is very advisable to use one of these abstractions instead of direct use of AES, if they fit your use-case.

It's also important to note that as of writing this, the sodium library has no implementation of memory allocation functions.

Please note you should "establish and utilize a policy and process for how cryptographic keys will be managed", protecting "master secrets from unauthorized access". That being said, your cryptographic keys shouldn't be hardcoded in the source code (as it is on this example).

Node.js's crypto module collects common cryptographic constants, and supports all cipher suits that are part of OpenSSL as stated in the documentation:

The algorithm is dependent on OpenSSL, examples are aes192, etc. On recent OpenSSL releases, openssl list-cipher-algorithms will display the available cipher algorithms.


1. Rainbow table attacks are not a weakness on the hashing algorithms.
2. Consider reading the Authentication and Password Management section about "strong one-way salted hashes" for credentials.

results matching ""

    No results matching ""