The JavaScript Cryptography API That You May Have Missed

John Au-Yeung - Jan 19 '20 - - Dev Community

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

The window object is a global object that has that provides JavaScript access to the DOM. It also contains a standard library of functions that can we access at any location in our web apps.

In this article, we look at the window.cryoto object.

window.crypto

The window.crypto property returns a Crypto object which is associated with the global object. This object allows web pages to run various cryptographic operations on the browser side. It has one property, which is the subtle property.

The Crypto.subtle property returns a SubtleCrypto object which allows us to do subtle cryptography on the client-side. The SubtleCrypto object has 5 methods for scrambling and unscrambling data. The sign method is for creating digital signatures.

A verify method exists to verify the digital signatures created by the sign method.

The encrypt method is used for encrypting data, and the decrypt method is used for decryption the scrambled data generated by the encrypt method. The digest method is used to create a fixed-length, collision-resistant digest of some data.

We can also use the SubtleCrypto object to generate and derive cryptographic keys with the generateKey and deriveKey methods respectively.

The generateKey method generates a new distinct key value each time we call it, while the deriveKey method derives a key from some initial material. If we provide the same material to 2 separate calls to deriveKey , we will get the same underlying value.

The deriveKey method is useful for deriving the same key for encryption and decryption. We can also use the importKey and exportKey methods to import and export cryptographic keys respectively.

There’s also a wrapKey method that exports the key and then encrypts it with another key.

An unwrapKey method is also provided to decrypt the encrypted key done by the wrapKey method and import the decrypted key.

For example, we can use the sign method to create a digital signature. It takes 3 arguments. The first is the algorithm, which is a string or an object that specifies the signature algorithm to use for creating the digital signature. Possible values are:

  • RSASSA-PKCS1-v1_5 — pass in the string “RSASSA-PKCS1-v1_5” or an object of the form { “name”: “RSASSA-PKCS1-v1_5” }
  • RSA-PSS — pass an RsaPssParams object. An RsaPssParams object has the name property which should be RSA-PSS , and saltLength which is the length of the random salt to use measured in bytes. The maximum value of saltLength is Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2
  • ECDSA — pass an EcdsaParams object. An EcdsaParams object has the name property, which should be the string 'ECDSA' and the hash property, which is string that can have the possible values of SHA-256, SHA-384 or SHA-512
  • HMAC — pass in the string “HMAC” or an object of the form { “name”: “HMAC” }

The second argument is the key which is a CryptoKey object that has the private key to be used for creating the signature. The third argument is the data which is an ArrayBuffer or ArrayBufferView object that has the data to be signed.

The sign method returns a promise that’s fulfilled with an ArrayBuffer object that has the signature.

Likewise, the verify method takes in the same first algorithm, key, and data argument as the sign method as the first, second and fourth arguments. The signature generated from the sign method is the third argument. It returns a promise that fulfills with the value true if the signature is valid and false otherwise.

To use the sign and verify methods, we can write something like the following code:

const enc = new TextEncoder();
const encodedMessage = enc.encode('hello');
const keyPair = window.crypto.subtle.generateKey({
    name: "RSASSA-PKCS1-v1_5",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["sign", "verify"]
);
(async () => {
  const {
    privateKey,
    publicKey
  } = await keyPair;
  const signature = await window.crypto.subtle.sign(
    "RSASSA-PKCS1-v1_5",
    privateKey,
    encodedMessage
  );
  const signatureValid = await window.crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, signature, encodedMessage);
  console.log(signatureValid);
})()

We first generate the key pair with the generateKey since we’re using the asymmetric RSA algorithm which has a private and public key. The generateKey method takes the algorithm as the first argument, where the possible values are:

The second argument is the boolean extractable property which indicates whether it’s possible to export a key using the SubtleCrypto.exportKey() or SubtleCrypto.wrapKey() methods. The third argument is the keyUsages array which indicates which methods can be used with the keys generated:

  • encrypt
  • decrypt
  • sign
  • verify
  • deriveKey
  • deriveBits
  • wrapKey
  • unwrapKey

Then we call the sign method with the algorithm name, private key, and the encoded message we generated from the TextEncoder . This generates the signature. Then we call the verify method with the algorithm name, private key, the generated signature fulfilled from the sign method and the same encoded message. If we run the code above, we should get console.log logging true .

The encrypt method takes 3 arguments. The first is the algorithm , which is an object with the following possible values:

The second argument is the CryptoKey object which we use to do the encryption. The third argument is a BufferSource object that has the data to be encrypted, also known as plain text. It returns a promise that’s resolved with an ArrayBuffer object containing the encrypted plain text.

Likewise, the decrypt method takes the same first 2 arguments as the encrypt method, except that the third argument is a BufferSource that has the data to be decrypted. It returns a promise that’s resolved with the ArrayBuffer object which has the plain text.

For example, we can use the encrypt and dercrypt methods like in the following code:

const enc = new TextEncoder();
const dec = new TextDecoder();
const keyPair = window.crypto.subtle.generateKey({
    name: "RSA-OAEP",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["encrypt", "decrypt"]
);
const encodedMessage = enc.encode('hello');
(async () => {
  const {
    privateKey,
    publicKey
  } = await keyPair;
  const encryptedText = await window.crypto.subtle.encrypt({
      name: "RSA-OAEP"
    },
    publicKey,
    encodedMessage
  )
  console.log(encryptedText);
  const decryptedText = await window.crypto.subtle.decrypt({
      name: "RSA-OAEP"
    },
    privateKey,
    encryptedText
  )
  console.log(decryptedText);
  console.log(dec.decode(decryptedText));
})()

In the code above, we first generate a key or key pair with the generateKey method with depending if the encryption algorithm is symmetric or asymmetric like we did with the sign and verify example. Asymmetric cryptographic algorithms have a public and private key like in the example above. RSA is an asymmetric algorithm.

Then we encode the message with the TextEncoder to encode it to a ArrayBuffer object which can used with the encrypt method.

Then we use the encrypt method with the algorithm, the public key, and the ArrayBuffer object with the encoded text passed int to encrypt the data. Then to decrypt the encrypted text, we use the decrypt method with the algorithm object passed in as the first argument, then we pass in the private key from the key pair, then we pass in the encrypted text as the third argument to the decrypt method.

This will get the decrypted data as an ArrayBuffer , which we will decode with the TextDecoder’s decode method with the decrypted ArrayBuffer to get back the original text. This means that the last console.log statement to get us back 'hello'.

The Crypto object also has one method, which is the getRandomValues method. The method will create a strong random value given a typed array. The method takes one argument. It takes a typed array, which is an Int8Array, a Uint8Array, an Int16Array, a Uint16Array, an Int32Array, or a Uint32Array. To improve performance, this method doesn’t generate numbers with a truly random number generator, but rather it uses a pseudo-random number generator to generate the number. The entries of the typed array passed into the argument will be overwritten by the random numbers generated by this method.

We can use the getRandomValues method like in the following example:

let array = new Uint32Array(10);
window.crypto.getRandomValues(array);
for (const num of array) {
  console.log(num);
}

In the code above, we generated a new Uint32Array, which we pass into the getRandomValues method. Then in the for...of loop, we get the generated values which overwrote whatever entries were in the original array. We should see 10 random numbers from the console.log, and each time we run the code above, we should get different results.

With the window.cryoto object, we can encrypt and decrypt data by using well-know cryptographic algorithms on the browser. It supports both symmetric and asymmetric encryption, which let us encrypt data with different algorithms. Also, we can use it to generate digital signatures and verify them. We can also use it to get random numbers with the getRandomValues method.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .