Advanced Encryption Standard

Cipher Block Chaining Mode

Part 3. AES CBC Encryption

Now it is time to combine techniques explained in previous articles with AES CBC encryption: our example will generate a secret key and a 16-byte initialization vector, then encipher a local file selected by the user. The secret key will be saved locally. The encrypted file and the IV will be converted into hexadecimal strings and transferred over the network as XML.

Binary input to the AES crypto engine is an instance of CryptoOperationData. The input bytes should be represented as an ArrayBuffer or an ArrayBufferView, so reading a local file as a buffer is a preliminary step before its encryption:

var fileSelector=document.querySelector("input"); // input element for file selection
if(fileSelector.files.length==0) {
 alert("Please select a file to encrypt");
} else {
 var file=fileSelector.files[0];
 var fileReader=new FileReader();
 fileReader.onload=generateAESKey;
 fileReader.readAsArrayBuffer(file);
}

The generateAESKey function is a named handler of the load event. The target of the event is the FileReader instantiated above. The result property of the FileReader returns the selected file as a buffer: this is the plaintext to be encrypted.

function generateAESKey(loadEvent) {
 var plaintext=loadEvent.target.result; // ArrayBuffer
 crypto.subtle.generateKey(algorithm, isExtractable, keyOperations).then(
  function(secretKey) {
   console.info("The secret key has been generated successfully.");
   var iVector=new Uint8Array(16);
   crypto.getRandomValues(iVector);
   var aesCbcParams={name: algorithm.name, iv: iVector}; // AesCbcParams object
   encryptFile(aesCbcParams, secretKey, plaintext);
  },
  keyGenerationFailure
 );
}

After the key has been generated, a 16-byte initialization vector created as a Uint8Array is populated with random values. The IV is used for the construction of an AesCbcParams object. Besides the IV, the AesCbcParams data structure has the name property reflecting the algorithm in action (AES-CBC). The AesCbcParams object, the key and the plaintext are passed to the encryptFile routine. These parameters will be required for calling the encrypt() method of the SubtleCrypto. If AES CBC encryption proves successful, the handler of the Promise fulfilled state receives the ciphertext as an ArrayBuffer:

function encryptFile(aesCbcParams, secretKey, plaintext) {
 crypto.subtle.encrypt(aesCbcParams, secretKey, plaintext).then(
  function(ciphertext) { // ciphertext is ArrayBuffer
   console.info("AES CBC encryption has been performed successfully.");
   convertAndSend(secretKey, ciphertext, aesCbcParams.iv);
  },
  function(eObj){
   console.error("AES CBC encryption has failed: "+eObj.message.toLowerCase()+".");
  }
 );
}

The encrypted file and its metadata are then stored in an XML document:

function convertAndSend(secretKey, ciphertext, iVector) {
 var parser=new DOMParser();
 var xmlDocument=parser.parseFromString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><document></document>", "text/xml");
 var fileName=xmlDocument.createElement("name");
 fileName.textContent=fileSelector.files[0].name;
 xmlDocument.documentElement.appendChild(fileName);
 var file=xmlDocument.createElement("file");
 file.textContent=ab2hex(ciphertext);
 xmlDocument.documentElement.appendChild(file);
 var iv=xmlDocument.createElement("iv");
 iv.textContent=ab2hex(iVector);
 xmlDocument.documentElement.appendChild(iv);
 . . .
}

The DOMParser API is used to create an HTML, XML or SVG document from a string. The parseFromString() method invoked with the text/xml MIME type returns an instance of XMLDocument. The child nodes of its root element will hold information about the encrypted file: these are the name of the file, hexadecimal representation of the encrypted data and the initialization vector. The ab2hex is an auxiliary function for converting buffers into hexadecimal strings:

function ab2hex(buffer) {
 var view=new Uint8Array(buffer);
 var hex="";
 for(var i=0; i<view.byteLength; i++) {
  var h=new Number(view[i]).toString("16");
  if(h.length==1){h="0"+h;} // zero padding
  hex+=h;
 }
 return hex;
}

The document can be uploaded to a remote server:

var xhr=new XMLHttpRequest();
xhr.open("POST", "http://example.com/document-handler.php");
xhr.send(xmlDocument);