Web Cryptography API
Modern Interfaces
When Web Cryptography was first introduced in September 2012, developers' reaction was a somewhat ambivalent. On the one hand, availability of client-side interfaces for basic cryptographic operations was a welcome trait of flagship browsers. On the other hand, JavaScript cryptography is considered insecure: it cannot replace TLS-based protection of HTTP message exchange.
Internet Explorer 11 was the first browser that implemented new APIs. However, IE's implementation reflects the early draft of the standard: the primary variant of the specification is centered on the concept of CryptoOperation and a set of callbacks for monitoring its progress. Gradual elaboration of the draft have changed the original approach to Web Cryptography: now it is established on JavaScript Promises. The Promise-based interfaces became stable features of Chrome, Opera and Firefox in 2014. Microsoft will update cryptographic APIs in the next release of its browser.
The code below will draw a comparison between the primary crypto interfaces and their final notation by calculating the SHA-256 message digest of a local file.
Crypto Object
Both old and new cryptographic functions creates a Crypto object:
console.log(window.msCrypto); // Crypto in IE 11
console.log(window.crypto); // Crypto in other browsers
Crypto can be used to generate a sequence of random numbers:
var rnd=new Uint8Array(8);
crypto.getRandomValues(rnd);
The getRandomValues() method provides access to a cryptographically strong RNG and populates the ArrayBufferView above with random numbers.
SubtleCrypto Object
The subtle property of Crypto instantiates a SubtleCrypto object: its methods deal with basic cryptographic operations including message digest computation.
console.log(crypto.subtle); // SubtleCrypto
CryptoOperation Object
Calling the digest() method of the SubtleCrypto in Internet Explorer 11 will create an instance of CryptoOperation. A set of callbacks should be attached to the object to reflect success/failure of the operation as well as its progress. The complete event is fired if the hash has been calculated successfully:
var hashFunction="SHA-256";
var fileSelector=document.querySelector("input"); // input element to select a local file
var fileList=fileSelector.files;
var fileIsSelected=(fileList.length>0) ? true : false;
if(fileIsSelected==true) {
var file=fileList.item(0);
var fileReader=new FileReader();
fileReader.onload=function(loadEvent) { // file has been read successfully
var buffer=loadEvent.target.result;
var message=new Uint8Array(buffer); // the data to be digested
var operation=window.msCrypto.subtle.digest({name: hashFunction}, message); // CryptoOperation
operation.oncomplete=function(evt) { // operation is complete
var messageDigest=evt.target.result; // message digest as ArrayBuffer
};
};
fileReader.readAsArrayBuffer(file);
}
else {
alert("Please select a file to hash");
}
The same operation could be performed by calling the process() and finish() methods of the SubtleCrypto:
var operation = window.msCrypto.subtle.digest({name: hashFunction});
operation.process(message);
operation.finish(); // result will be returned asynchronously
operation.oncomplete=function() {
console.log(operation.result); // message digest as ArrayBuffer
};
In addition to the complete handler, callbacks are specified for the abort, error and progress events.
Modern Web Cryptography API: Promise-Based Operations
Let's rewrite the example above resting our code on the modern interfaces:
var hashFunction="SHA-256";
var fileList=fileSelector.files;
var fileIsSelected=(fileList.length>0) ? true : false;
if(fileIsSelected==true) {
var file=fileList.item(0);
var fileReader=new FileReader();
fileReader.onload=function(loadEvent) { // file has been read successfully
var buffer=loadEvent.target.result;
window.crypto.subtle.digest({name: hashFunction}, buffer).then(
function (rawDigest) { // anonymous handler of the Promise fulfilled state
console.log(hashFunction+" computation has been performed successfully.");
. . .
},
function(eObj) { // anonymous handler of the Promise failed state
console.error(eObj);
}
);
};
fileReader.readAsArrayBuffer(file);
}
else {
alert("Please select a file to hash");
}
An attempt to calculate a hash value creates a new Promise. If the digest computation proves successful, the Promise moves from unfulfilled to fulfilled state. The handler reflecting the state of the fulfilled Promise receives the message digest as an ArrayBuffer. If the requested operation has not been performed, the Promise moves to the failed state: in this case any errors are handled in a separate routine.
As a final stroke, the binary digest is converted into a hexadecimal string:
. . .
var digestView=new Uint8Array(rawDigest);
var hash="";
for(var i=0; i<digestView.byteLength; i++) {
var hex=new Number(digestView[i]).toString("16");
if(hex.length==1){hex="0"+hex;}
hash+=hex;
}
console.info("The file \'"+file.name+"\' has the following "+hashFunction+" digest: "+hash+".");