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 areaes192
, etc. On recentOpenSSL
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. ↩