How to build a crypto isomorphic library with Javascript and WebAssembly

Let’s face it: we love secrets. We share secrets with our best friends, we keep secrets from our parents and write secret journals to tell them our deepest thoughts. Across the centuries this natural inclination has evolved from the lunacy of a few mathematicians into an actual science taught in universities all over the world. In the past, the art of keeping secrets that we now call cryptography was almost solely used to privately exchange information between countries. In the present, it’s ubiquitous.

“The world runs on codes and ciphers, John. From the Million-pound security system at the bank to the Pin machine you took exception to. Cryptography inhabits our every waking moment” — Sherlock Holmes

With the advent of the internet, in fact, privacy has become a primary concern. During the World War II, the Enigma machines were crucial for determining Germany’s triumph and defeat. But in the internet age, the information is natively digital and every computer is a super advanced Enigma machine. It is not surprising then that we have encapsulated our desire for secrecy into code, entrusting computer systems themselves to keep secrets on our behalf. The art of secrecy is no more about stealing cyphers from the enemy field; rather, it is about building machines that are able to communicate with each other without any third party deciphering the message. Nowadays, cryptography has become an indispensable tool for protecting information: our bank account, our emails, our online browsing habits, they are all secured by cryptography. The range of applications is immense. Cryptographic communication is used to privately exchange messages from miles away in just a fraction of a second, but also to safely store personal data that we alone could read back. In the modern world, cryptography is the glue that keeps the world attached.

In the face of all this, privacy does not simply mean freedom from public scrutiny, but also personal safety and is indeed a fundamental human right and must be protected as such.

The quest for the Holy Grail in web crypto libraries

Here in Cubbit we have the dream of decentralizing the data infrastructure and to do so we need a web application on which users are able to save and access their files from anywhere in the world. That’s why we began our search for available web crypto libraries running on the browser. Although a lot of them popped up over the years, no one suited our needs regarding encryption speed and usability for three main reasons:

  • Overly complicated APIs.
  • Poor performances when encrypting/decrypting files (cryptography is a computationally intensive application and has typically performed poorly in Javascript).
  • Lack of standards that we need to build our crypto architecture.

Reason #0: web crypto libraries are fiddly

Let’s explore these reasons in depth.

First, we assume that OpenSSL is the de facto cryptographic standard, considering many companies are adopting it in their security workflow. However, since it was written in C, it carries awful API signatures and is still poorly documented, making its learning curve steeper than other technologies. So, all the emerging crypto libraries are striving to maintain their codebase as compatible with OpenSSL as possible, with the result of not simplifying their API for new developers.

Reason #1: crypto on the web is insecure AND slow

Secondly, the evolution of web technologies has resulted in browsers adopting only Javascript for the sake of simplicity. Nevertheless, Javascript is not the best language for computational heavy tasks, and guess what, cryptography is a computational heavy task. Also, given its increasingly widespread adoption, it is often the first choice by many budding developers at their first experience. Result: many of them started writing bad code, making the language full of security issues.

Reason #2: lack of support for ECC and other standards across many libraries

Finally, none of the libraries we found includes all the standard algorithms we need to implement; for instance, none supported Elliptic-curve cryptography. What’s more, each one of them is suited for a particular encryption protocol more than others, and we make use of a plethora of encryption protocols.

Let’s start

To overcome all these problems we started developing our crypto library in-house. We designed it with three clear goals in mind:

  1. Isomorphism
  2. File encryption speed
  3. Never reinvent the wheel unless it’s squared

Isomorphism refers to the concept of universal code, i.e. write once, run anywhere.

File encryption speed means that our crypto library should be fast at encrypting files in order to deliver the best possible user experience.

The third principle is our approach to code. In other words, since time is the most valuable resource in the universe, it should never be wasted if efficient solutions already exist. Neither should be wasted if they don’t by trying to get blood out of a stone. Building a new solution, in such a case, is always the best and fastest path.

On the web

We spent hours and hours (re)searching a library with the features and the performances we needed without any success. So we decided to try the compilation path.

What? Compile Javascript? How?

Thanks to Emscripten!

Emscripten is a quite new technology with some very interesting claim, citing:

Emscripten is a toolchain for compiling to asm.js and WebAssembly, built using LLVM, that lets you run C and C++ on the web at near-native speed without plugins.

Worth a try, don’t you think?

We started from the ED25519 implementation. Emscripten has its own library that has to be included in the bindings.

An important consideration is about memory management. In the Javascript world, we, as developers, are used to believe in the Greatest Garbage Collector that allows us to simply ignore every memory-related aspects. But in order to successfully use the Emscripten code, we are forced to take care of the shared memory with malloc and free.

const heap_seed = em_array_malloc((self as any).enigma, seed);
ED25519.create_keypair(heap_seed.byteOffset, seed.length);
em_array_free((self as any).enigma, heap_seed);

With this working result in the bag, we moved to another topic: AES streaming mode. We didn’t find very good performances during our research and so we choose to try with OpenSSL. This implied that the codebase had to be compiled with Emscripten; unfortunately, though, documentation was lacking. So after some times we empirically found the procedure to achieve the results,

that is to say, the following steps:

  1. Exclude all ciphers that are not strictly required
  2. Cross-compile
  3. Link with your code

And…

On Node.js

Typescript, the typed superset of Javascript by Microsoft, is the hearth of the tech stack here in Cubbit. Thus it would have been natural to build the cryptographic stack in Typescript as well. However, performances and cryptography fit in perfectly with native languages such as C++, while, as we’ve said, Typescript is inappropriate for the task.

So we decided to go with Node.js as the primary language, since it lets us leverage add-ons to easily integrate native code. In this regard, N-API is a great tool by the Node.js team, an API that is stable across Node.js versions: just build once and that will run on every later major version without recompilation.

What’s more, it’s quite simple to use as well:

Just wrap and export the objects and functions that you want to share with the Javascript world and let the compiler do the hard work for you.

Enigma, a fast universal crypto library

And so, after a few months of research and coding, Enigma had come to life.

Enigma is a crypto library designed to work efficiently on browsers by leveraging on cool technologies such as WebCrypto and WebAssembly. Our aim is to provide the major crypto standard algorithms as well as a suite of utilities to simplify the use to the “standard” developer.

To further facilitate its adoption, we made Enigma isomorphic, i.e. compatible both with Node.js and the Web so to make it interoperable without changing a single line of code. The code should be universal, according to the WORA motto, i.e. “write once, run anywhere”.

How to use it

Now you may say: “Ok, cool! But how can I use it?” First of all, you need to install it. Being Enigma an NPM module, you can simply type the following command in your terminal:

npm install @cubbit/enigma

or if you prefer yarn:

yarn add @cubbit/enigma

Now Enigma is installed and part of your dependencies.

It’s time to start using it then!

Let’s say you want to encrypt a message with AES-256 (just to start easy). Here we go. First thing first, we need to initialize Enigma. Since it makes use of native code to boost the performance we need to wait the browser to load it before we can start using the library.

NOTE: The following code is written in Typescript, but you can also write in pure Javascript of course!

Our goal here is to encrypt a simple text string.

Beware that, since AES256 is a symmetric encryption algorithm, we need to use the same key we used to encrypt the message to decrypt it back. Something like:

Let’s say now we want to encrypt a file before sending it to the network. Well, you need to know that most of the existing browsers have limitations concerning the maximum memory that can be allocated by a web page. For example, Google Chrome can allocate a maximum of 1.5GB of RAM. Also, generally speaking block encryption algorithms work better with small chunks of data. Consequently, it is not advised to encrypt the whole file but to encrypt it progressively. In this field, Node.js provides a very handy and well-designed tool to manipulate streams of data: the Stream object. So we took inspiration from it to provide a seamless API.

The power of this kind of syntax is that you can attach pipes together to automatically send the encrypted file over the network while it is being encrypted. Magic! :)

file_stream.pipe(aes_stream).pipe(socket);

Enigma provides a lot more features, so if you are interested feel free to visit its repository and contribute.

The results

We run a suite of benchmarks to compare Enigma to other existing libraries for the web. Here are the results (Chrome 72 on I7-7820HQ - lower is better):

AES encryption. Lower is better

As you can see, Enigma performs best when encrypting and processing files rather than encrypting small strings. In fact, we are introducing a small overhead by loading the web assembly binaries but it is game-changing when we need to encrypt larger amount of data such as files.

Our vision for the future of Enigma

Our goal is to create a cryptographic library both powerful and simple.

We will keep wrapping libraries with our APIs to implement more algorithms and achieve better performances. We have designed Enigma with usability and performances in mind, and we’ll keep working to improve it.


PS. Enigma has been developed as the cryptographic layer for Cubbit, which is now kickstarting its distributed cloud. If you want to know more, check it out here.