A while ago it was announced that a crypto api will be released for javascript. Meaning it will be possible to do real encrypting and decrypting, and generating hashes directly in the web browser. Now it's actually possible! It should be noted though that even if the api is a candidate recommendation, the browser support isn't the best just yet. When this is written most modern browsers supports it, except internet explorer. But given some time it should surely have improved.

Of course there are always javascript plugins and polyfills that can be used as fallbacks in the meantime. Also node packages can be implemented with for example browserify, should one really want to get started today to encrypt and hash things in javascript.

This is something really great since it gives us the possibility to do things like computing authentication codes, signing and verifying documents, encrypting and decrypting values, importing and exporting keys, generating new keys, and even generating cryptographically strong random numbers in the browser.

The crypto api will surely open up a lot of doors for web development. But there's actually a couple different opinions if this is really a good or a bad thing. In this post we won't dive very deep into that, instead we'll just focus on how it looks like and works. And how to get started using it, meaning doing some real encrypting and hashing in javascript.

Browser support

// Returns if browser supports the crypto api
function supportsCrypto () {
return window.crypto && crypto.subtle && window.TextEncoder;
}

The first thing we should do is check if the visiting users browsers supports the javascript cryptography api. The example above checks if crypto.subtle and the TextEncoder class are available, meaning it's good to go.

Generate a hash in javascript

// Hash function in javascript
function hash (algo, str) {
return crypto.subtle.digest(algo, new TextEncoder().encode(str));
}

hash('SHA-256', 'Hello').then(hashed => {
console.log(hashed); // ArrayBuffer
console.log(hex(hashed)); // 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
console.log(encode64(hashed)); // GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=
});

Now when we know we have the browser support we can go ahead and generate a hash. To do this we use the crypto.subtle.digest method. It's an asynchronous method that returns a promise, which is why the method call "then" is used in the example. But the example could also be rewritten to use a callback function instead, or with the await and async syntax.

The method takes two arguments. The first is what algorithm should be used and can be one of the following:

  • SHA-1
  • SHA-256
  • SHA-384
  • SHA-512

Not too many hash algorithms are available for the moment, but it's sure to be more in the future. The second argument is the value that should be hashed. It must be of type ArrayBuffer so we first convert the string into that with the TextEncoder class before passing it.

There are no hex or base64 functions in the crypto api. Instead we're supposed to work with ArrayBuffers (or Uint8Arrays) in javascript. If you want to convert to hex or base64 the below functions can be used, which converts directly from ArrayBuffer types.

// Hex function for ArrayBuffer
function hex (buff) {
return [].map.call(new Uint8Array(buff), b => ('00' + b.toString(16)).slice(-2)).join('');
}

// Base64 encode
function encode64 (buff) {
return btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
}

Generating cryptographically strong random values

// Random numbers
var values = crypto.getRandomValues(new Uint8Array(12));
console.log(values); // [216, 35, 236, 7, 193, 158, 115, 140, 64, 74, 177, 155]

There are many different ways of generating random values in javascript. For example the method Math.random is widely used for doing this. But when it comes to encryption we need to be able to generate cryptographically strong random values that can't so easily be guessed. In the api there's a method called getRandomValues for just that purpose.

It takes a typed array as parameter, like an Int8Array, Uint8Array, Int16Array or Int32Array etc, fills it up with random numbers and then also returns it. So if you pass it an array with the length of 12, you'll get one back filled with 12 random numbers. This method is also good for generating initialization vectors for example, which is how it's used in the section below.

Encrypting values in javascript

// Generate key from password
async function genEncryptionKey (password, mode, length) {
var algo = {
name: 'PBKDF2',
hash: 'SHA-256',
salt: new TextEncoder().encode('a-unique-salt'),
iterations: 1000
};
var derived = { name: mode, length: length };
var encoded = new TextEncoder().encode(password);
var key = await crypto.subtle.importKey('raw', encoded, { name: 'PBKDF2' }, false, ['deriveKey']);

return crypto.subtle.deriveKey(algo, key, derived, false, ['encrypt', 'decrypt']);
}

// Encrypt function
async function encrypt (text, password, mode, length, ivLength) {
var algo = {
name: mode,
length: length,
iv: crypto.getRandomValues(new Uint8Array(ivLength))
};
var key = await genEncryptionKey(password, mode, length);
var encoded = new TextEncoder().encode(text);

return {
cipherText: await crypto.subtle.encrypt(algo, key, encoded),
iv: algo.iv
};
}

encrypt('Secret text', 'password', 'AES-GCM', 256, 12).then(encrypted => {
console.log(encrypted); // { cipherText: ArrayBuffer, iv: Uint8Array }
});

Encryption is a bit more complicated than hashing, and there are more things that can go wrong. One mistake could mean a vulnerability to your encryption, so it's very important to understand what everything does in your algorithms.

This implementation is an example on how to do encryption and decryption using passwords. It's possible to do it without passwords, by instead generating keys automatically for your users or importing already existing ones. The keys can then later on be stored somewhere or exported, depending on how your application is supposed to work.

In these examples also the async and await syntax are used, which is a pattern that makes the code a bit more readable when working with promises. If you're already familiar with promises it doesn't take long to learn.

So let's get started. First we need to decide what algorithm to use which can be one of the following:

  • AES-CBC
  • AES-CTR
  • AES-GCM
  • RSA-OAEP

There are a few more to choose from, but not all of them are safe to use so they've been excluded from this post for now. There's a good lookup table at https://github.com/diafygi/webcrypto-examples on all available algorithms and shows which ones should be used for what. Here we'll only use the algorithm AES-GCM which is the Advanced Encryption Standard in Galois/Counter Mode. For the moment (Feb 2018) it's considered one of the most secure to use.

The AES algorithm we're going to use can have a key length of either 128, 192 or 256, which is set with the property length. We'll be using a 256 bit key. Other algorithms works in different ways and have other properties that needs to be set for them.

Next thing is to create an initialization vector (iv). It should be a unique sequence of random numbers for every encryption process, to make them more random and to prevent dictionary attacks. How long they should be depends on the mode being used. For GCM the length should be 12, but if you're using another mode the length could be different. A common mistake here is thinking the iv needs to be kept secret. That's actually false and it can without problems be sent out in the clear together with the encrypted text. Your password on the other hand should to be kept secret.

An encryption key is then required, which we've here abstracted into its own function, since it requires a couple steps to be done right. A good way is to use the PBKDF2 algorithm and then derive it into a key with our encryption mode.

To then perform the actual encryption the subtle.encrypt method is used with an algorithm object, the key, and the plain text as arguments. The text is only accepted as a byte stream, so we must first encode it with the TextEncoder class. Finally the encrypted text is returned along with the iv as an object. Waiting to be decrypted, which is what the next section is all about.

Decrypting values

// Decrypt function
async function decrypt (encrypted, password, mode, length) {
var algo = {
name: mode,
length: length,
iv: encrypted.iv
};
var key = await genEncryptionKey(password, mode, length);
var decrypted = await crypto.subtle.decrypt(algo, key, encrypted.cipherText);

return new TextDecoder().decode(decrypted);
}

(async () => {
var mode = 'AES-GCM',
length = 256,
ivLength = 12;

var encrypted = await encrypt('Secret text', 'password', mode, length, ivLength);
console.log(encrypted); // { cipherText: ArrayBuffer, iv: Uint8Array }

var decrypted = await decrypt(encrypted, 'password', mode, length);
console.log(decrypted); // Secret text
})();

Now when we have encrypted some text, we want to be able to decrypt it and retrieve the hidden secret value it contains. This is done with the crypto.subtle.decrypt method which works in a similar way, just reversed.

The exact same algorithm properties that the encryption process had must be passed for it to work. Meaning the mode, key length and the password, which here again is generated into a new key in the same way. The iv was stored together with the cipher text so it can be passed to the algorithm in an easy way. If any of these are wrong the process will fail.

If everything went well we can lastly decode the buffered value into a string with TextDecoder and then return it. We have now successfully encrypted and decrypted a value in javascript!

Further reading

That's all for now. When the crypto api in javascript has gotten some better browser support and starts being used for real, this post will probably be updated with more of its features. Like for instance how to generate key pairs, verify keys and sign documents. Further reading and how the development is going can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API.

A final note. This is by no means written by a security expert, so don't use the examples in this post in a real project. It's just an introduction to the api, and the code examples might not be 100% secure to use.