The challenge we faced when creating the Opal website was to find a reliable way to encrypt and decrypt in the browser.
This article describes the challenges faced by browser-side encryption and identifies a solution provided by recent technological advances.
Three options for encryption in web applications
Only JavaScript is the language supported by all browsers. Web applications like Opal are written in JavaScript to run on any modern browser. If these applications are to use cryptographic functions, JavaScript must be able to access them.
Currently, there are only three options to expose encryption functions to the browser's JavaScript:
1. Use plug-in encryption
Plug-ins refer to compiled code that runs in the browser and can be called by JavaScript.
For example, encryption libraries that exist in Java and Flash. This approach is usually very performant, but requires the user to install a browser plug-in, which is something people are unwilling or unable to do (if they are using a public computer).
Another option is to use the Chrome browser’s NaCl client (Native Client) program, which allows running machine code compiled from C or C++. Again, this approach is very performant, but the NaCl client program can only be used with the Chrome browser.
Even though these plug-ins and NaCl client programs have advantages in speed, because they require users to use special plug-ins, or use a specific browser, the portability of this approach is not very good.
2. Use Web Encryption API
The upcoming Web Encryption API will provide JavaScript with a native basic encryption interface, allowing Web applications to encrypt and decrypt faster. However, this interface is still in the draft stage, and it will be a long time before mainstream browsers adopt this technology. Now, the only function that can be used in most browsers is the crypto.getRandomValues() function.
This is not a practical browser-side encryption solution until the Web Encryption API is widely used.
3. Directly use JavaScript to encrypt
The advantage of this solution is its high portability. All browsers can execute JavaScript, which means that all browsers can call encryption libraries written in JavaScript.
Encryption in JavaScript has two main drawbacks: security and speed. We'll talk about these two flaws in turn.
JavaScript encryption can become secure
An article claims that "JavaScript encryption is harmful" and lists many evidences to support this statement.
Some of the views in the article are no longer accurate. For example, this article says that the Math.random() function is not a good source of random numbers, so it is impossible to get enough random numbers for encryption. The Math.random() function is indeed not a good source of random numbers. Modern browsers already provide the crypto.getRandomValues() function to obtain a sufficient number of random numbers.
There are quite a few examples in this thread that prove JavaScript encryption is a bad idea, but it also makes sense.
This answer helpfully refutes many of the arguments in the first post, and also points out two valid use cases for JavaScript encryption: end-to-end message encryption (i.e. applications that protect against host access) and secure Remote password authentication. These are exactly the usage scenarios of Opal encryption, so it is very natural for us to use JavaScript encryption.
JavaScript Encryption Can Be Fast
Until recently, JavaScript was slow at performing the complex calculations required for secure encryption. This directly results in many applications relying on the encryption functions provided by plug-ins, which is not portable and annoying to users.
Fortunately, the performance of JavaScript has greatly improved in recent years, so it is feasible to use JavaScript entirely for encryption operations. There are many JavaScript encryption libraries to choose from now (link 1, link 2, link 3, link 4, link 5, link 6, link 7, link 8, link 9).
So it becomes a question of which library to choose.
NaCl, a reliable C language encryption library
NaCl (pronounced “salt”) is a C language library that provides application functions for symmetric key encryption, decryption and public key signature authentication. It is written by cryptographers and is well known and trusted in the crypto community. One of the problems is that NaCl is written in C, not JavaScript.
js-NaCl: Compile NaCl into JavaScript
Fortunately, we can compile NaCl into LLVM bytecode, and then use emscripten to compile these bytecodes into JavaScript. Moreover, the LLVM compiler can perform many optimizations during compilation, so the resulting JavaScript code will also be optimized. So we can compile the NaCl library into JavaScript, ready to run in the browser! The
js-nacl project is exactly that: a NaCl encryption library compiled into JavaScript.
asm.js is fast!
Even better, the code compiled by emscripten is a subset of JavaScript, also called asm.js. You can think of asm.js as an assembly language much like JavaScript. When the browser encounters a code block of asm.js, it will compile it into efficient machine code and run at a speed close to native code.
Currently, only Firefox browser supports asm.js optimization. This makes js-nacl encryption and decryption in Firefox very fast, 2 to 8 times faster than the Chrome browser, depending on the specific operation. But even for Chrome, js-nacl is fast, beating every other encryption library we tested.
A trusted encryption library like NaCl and fast execution in modern browsers make it a good idea for web applications like Opal to use the js-nacl library.
For the same reason, Opal uses the asm.js version of the scrypt library compiled by emscripten to expand the key (being enabled in this article). You can see a performance comparison of js-nacl and js-scrypt provided by the project maintainers. We also conducted jsperf testing for js-nacl to understand the performance differences between different browser versions. You can also try it as you like.