Encryption with JavaScript — Part 2

Welcome back to my series on encryption with javascript. In Encryption with JavaScript — Part 1 we went over some of the different libraries that exist in the JavaScript space and chose NodeJS's Crypto Module for our examples.

Today we'll see what a cipher algorithm is, what different options we have and information on cipher keys (also known as encryption secret).

Cipher Algorithms & Keys

Coming back to our encryption function...

import { createCipheriv, randomBytes } from 'crypto'

export const encrypt = (text: string, key: string): string => {
  const iv = randomBytes(96)
  const cipher = createCipheriv('id-aes256-GCM', Buffer.from(key, 'hex'), iv)
  const ciphertext = cipher.update(text, 'utf8', 'hex') + cipher.final('hex')
  const authTag = cipher.getAuthTag().toString('hex')
  return ciphertext + '|' + authTag + '|' + iv.toString('hex')
}

After the imports comes the encrypt function definition.

const encrypt = (text: string, key: string) => {

Nothing much to see here, we're just declaring a function that takes some text to encrypt and a key or password to encrypt it with.

Let's look at the first lines of the function now:

  const iv = randomBytes(96)
  const cipher = createCipheriv('id-aes256-GCM', Buffer.from(key), iv)

Here's were most of the magic is happening. Even though it's only two lines of code, there's a lot going on! We're calling the createCipheriv function from Node's Crypto module to create our cipher object, which we'll use to perform the actual encryption.

Remember that we said this module is just a wrapper for OpenSSL? Well, createCipheriv is the main function we use to interact with it.

We're passing three arguments to it. The first argument, the algorithm, tells OpenSSL which algorithm to use to perform the encryption. What encryption/decryption algorithms are available depends on the version of OpenSSL being used by NodeJS.

The list of algorithms supported by openssl can be obtained running openssl list -cipher-algorithms.

$ openssl list -cipher-algorithms
AES-128-CBC
AES-128-CBC-HMAC-SHA1
AES-128-CBC-HMAC-SHA256
id-aes128-CCM
AES-128-CFB
AES-128-CFB1
AES-128-CFB8
AES-128-CTR
AES-128-ECB
id-aes128-GCM
...and 160 more...

Each line or algorithm consist of a cipher, key length, operation mode and optionally some other parameters or groupings.

For example, AES-128-CBC means "use the cipher AES with a key length of 128 bits and the operation mode CBC".

As you can see, the output can be a bit long, so try greping some parameters out and sorting the output. For example, to list all available modes of operation for aes256:

$ openssl version
OpenSSL 1.1.1c  28 May 2019
$ openssl list -cipher-algorithms | grep -i aes | grep 256 | sort
AES-128-CBC-HMAC-SHA256
aes256 => AES-256-CBC
AES-256-CBC
AES-256-CBC-HMAC-SHA1
AES-256-CBC-HMAC-SHA256
AES-256-CFB
AES-256-CFB1
AES-256-CFB8
AES-256-CTR
AES-256-ECB
AES-256-OCB
AES-256-OFB
aes256-wrap => id-aes256-wrap
AES-256-XTS
id-aes256-CCM
id-aes256-CCM
id-aes256-GCM
id-aes256-GCM
id-aes256-wrap
id-aes256-wrap-pad

Lines with a => in them are aliases. For example aes256 => AES-256-CBC means that if you pass just aes256 to it, you'll actually be using AES-256-CBC.

The big question now is: which do we choose? Why did we choose id-aes256-GCM and not DES-CBC, for example?

We can break this down into three choices: first, we need to decide which cipher to use, then, which key size, and last but not least, which mode of operation.

Ciphers

Ciphers are the algorithms that perform encryption or decryption.

They can be categorized in two main groups: block ciphers and stream ciphers. Block ciphers act on fixed-length group of bits (a "block"), stream ciphers work byte-by-byte (or bit-by-bit) instead.

There are many cipher algorithms available, but only so few that make a reasonable choice.

For the past years and foreseeable future, AES (Advanced Encryption Standard) is, has been and will be the gold standard. It's used by pretty much everybody, including the U.S government; has seen a lot of cryptanalysis, is implemented in basically every symmetric cryptography library and has had hardware support since 2010, which makes it really fast.

Another option that appeared more recently is ChaCha20/Poly1305, which so far has survived many attacks and audits. The main advantage of this cipher is that it runs much faster than AES on systems which don't have hardware accelerated support for AES-GCM such as mobile phones, which in turn drains less battery.

For this very reason Google added it to TLS in Chrome and Android in 2014, to which CloudFare followed a year later and Firefox in 2016.

But how did AES come to be the de-facto choice?

AES, Demolition Derbies and Standards Institues

Up to the 90's DES was the most widely used encryption algorithm. By then it was starting to show its weaknesses, and cryptographers suspected it may have been designed with backdoors that only the NSA knew.

In 1997 North America's National Institute of Standards and Technology started a years-long process to find a cipher algorithm that would supersede DES. The winning algorithm, whichever it was, would be named AES.

In total, 15 algorithms were created and submitted. Out of these, Rijndael, proposed by Belgian cryptographers Vincent Rijmen and Joan Daemen, was chosen as the winner of competition in 2000 and published as a standard in 2001.

To us, AES is the shining example of how to standardize security systems. AES is not a design by committee, but rather a design by competition.

~ Cryptography Engineering, Bruce Schneier.

Interestingly, DES was proven broken in 1999, making the AES competition very timely.

AES was adopted by USA's government to protect classified data, which is probably why marketing teams like to say "military-grade encryption", regardless of whether this encryption was implemented securely.

In my opinion, it makes sense that, since USA trusts AES to preserve its secrets and that cryptographers were happy with the process, governments and private companies from all over the world would also trust this algorithm. Fast-forward a few years and most of the world's secrets are encrypted with AES.

Other Ciphers, Competitions and Standards

Although Rijndael won the AES title, there were actually 5 finalist in the contest, all of which USA's NSA considered secure enough for its government's non-classified data. These were MARS, RC6, Serpent and Twofish.

Though most famous, USA is not the only country to have pushed and hosted cryptographic competitions and research and to have published standards.

The European Union, for example, had NESSIE in 2000-2003, followed up with eSTREAM from 2004 to 2008; and ECRYPT I & II in 2012. Japan had CRYPTREC in 2003. The international, non-governmental CAESAR took place from 2013 to 2019. All of these were similar to USA's AES process.

These competitions yielded or pushed forward ciphers like Deoxys-II, AEGIS, Camellia and Salsa20 (this last one evolved into ChaCha20).

It's also worth noting that a government standardizing and/or pushing forward a cryptographic primitive is not an absolute seal of approval. For example, Russia's Kuznyechik may have been designed with a backdoor, and USA's NSA tried to push their Simon & Speck algorithms for standardization in a suspicious manner, to say the least.

Old Ciphers

A few cipher algorithms were popular in the past but are now well-know to be unsafe or even completely broken, and should be avoided. In other cases, issues with patents or lack of speed make some ciphers undesirable. Finally, some algorithms have been here for a long time and, although no good attack is known, they never found widespread use, making it unlikely for them to become popular in the future.

Some examples are DES and its follower TripleDES, Blowfish, Twofish and IDEA. If you're feeling adventurous, take a look at Bruce Schneier's 1996 book, Applied Cryptography, chapter 13: Other Block Ciphers. There are 12 of them just there, and I don't think any of them are alive right now.

What's Next?

We still don't know. Several past ciphers have been broken; it logically follows that AES will eventually follow suit, specially with the advent of quantum computing.

Right now, should AES fall, ChaCha20/Poly1305 seems to be the best backup. The contrary also applies: if Chacha/Poly falls, AES will still be there. Whenever this happens, the need for a backup cipher will become urgent and governments, organizations and cryptographers will likely prioritize its development.

Regarding quantum computers, they seem to threaten asymmetric (public key) cryptography mainly, leaving symmetric crypto largely safe. In 2016 NIST began a Post-Quantum Cryptography competition, which as of 2022 is still ongoing, for which many public-key and digital signature algorithms were submitted. In theory, we should see a standard in 2023, but the most popular submission, Rainbow, turned out to be easily broken with a common, consumer-grade computer. We'll have to wait and see.

Passwords, Keys and Key Lengths

Let's take a look at the cipher instantiation again:

const cipher = createCipheriv('id-aes256-GCM', Buffer.from(key), iv)

This second parameter for the cipher algorithm, 256 in this case, is the key length or key size, which is always expressed in bits, not bytes.

Ciphers use keys to encrypt and decrypt data. In a sense, a key is like a password. Unlike a password, which is usually human readable text made of characters with encoding, keys are just a sequence of bits.

Different block ciphers support different key lengths. AES and Camellia, for example, support 128, 192 and 256; while Salsa20 and it's variant ChaCha20 always use 256.

The chosen key length determines how many possible different keys there are. This is important because all cryptographic algorithms, no matter how secure they are designed, implemented and used, can be violated by brute-force attacks. For example, if we had a key length of 4 bits, that would get us 32 different keys. If decryption of a 1MB file took 1 second, trying all keys would take 32 seconds, and you could probably get better decryption speeds with 20 year old computers.

How long decryption takes depends on the block cipher, hence so does the threshold of what key size is brute-forceable and what's not. In general it seems to be somewhere between 70 and 90 bits. Since all three key lengths supported by AES are above that, they can all be considered safe.

Even though 128-bits keys are theoretically unbreakable and even considered safe for classified information by the U.S. government, as computing power increases and new techniques and technologies are developed, larger keys may become a requirement. That same document states top secret information should use 192 or 256-bit keys. Daniel J. Bernstein, in his Salsa20 white paper, posits 128-bit keys will be regarded as uncomfortably risky in the future. Still, brute-forcing a 128-bit key should take billions of years with the best hardware, cryptanalysis techniques available and known attacks.

The design and strength of all key lengths of the AES algorithm (i.e., 128, 192 and 256) are sufficient to protect classified information up to the SECRET level. TOP SECRET information will require use of either the 192 or 256 key lengths. The implementation of AES in products intended to protect national security systems and/or information must be reviewed and certified by NSA prior to their acquisition and use.

National Policy on the Use of the Advanced Encryption Standard (AES) to Protect National Security Systems and National Security Information, 2003.

So, in general, a larger key should be more secure, or at least more future-proof, especially with the advent of quantum computing.

This may not always be the case, though. AES (and other ciphers) internally derives several 128 bit keys from the provided key in a process known as key schedule. This process is slightly different for AES-128 and AES-192, and this small difference makes AES-256 more vulnerable to some attacks. Even though these attacks are only theoretical and practically impossible to implement in real life, Bruce Schneier, author of the Blowfish and co-author of the Twofish ciphers, suggests using AES-128 if developing a new application.

Bottom line, 128 bits is a safe bet for AES. If you are feeling extra paranoid, you can encrypt twice, once with 128 bits and once with 256.

Conclusion

So, which do we choose? As they said, no one has ever been fired for choosing AES.

Regarding cipher key lengths, which to choose depends on which algorithm we're using. For AES, both 128 and 256 are safe choices.

That's it for today. In the next entry in this series we'll explore what's possibly the most interesting part: modes of operation.

Next up is the grand finale: Encryption with JavaScript — Part 3. Which I'll finish some day. I promise..

A newsletter for programmers

Yo! This is Taro. I've been doing JavaScript for years and TypeScript for years. I have experience with many programming languages, libraries, frameworks; both backend and frontend, and in a few company roles/positions.

I learned a few things over the years. Some took more effort than I wish they had. My goal with this blog and newsletter is to help frontend and backend developers by sharing what I learned in a friendlier, more accessible and thorough manner.

I write about cool and new JavaScript, TypeScript and CSS features, architecture, the human side of working in IT, my experience and software-related things I enjoy in general.

Subscribe to my newsletter to receive notifications when I publish new articles, as well as some newsletter-exclusive content.

No spam. Unsubscribe at any time. I'll never share your details with anyone. 1 email a week at most.

Success!
You have subscribed to Taro's newsletter
Shoot!
The server blew up. I'll go get my fire extinguisher — please check back in 5.