A random story on WebCrypto and WebAssembly

Leo Selig
neXenio
Published in
9 min readMay 9, 2019

In an age where data breaches only make it into the news if at least a couple of hundred million records are stolen, thinking about how to protect your personal or your business' data becomes more and more important. While tools like GPG have allowed tech-savvy people to protect our data for a while already, there is a lack of solutions that are easy enough for non-technical persons to use.

Sourcing entropy from the user in Bdrive

Bdrive is an end-to-end encrypted (E2E) data sharing platform that aims to combine state-of-the-art security methods with a user interface that everyone, even non-digital natives, can use quickly and correctly. This ease of use is especially important since a bad UX will likely cause users to bypass the software's intended secure workflow.

I am one of the software engineers at neXenio, the company behind Bdrive. Specifically, my team is working on the browser single page applications for Bdrive. During our work we repeatedly observed how combining E2E-encryption with a great UX is a big technical challenge for our applications and thus us as developers. A lot of data needs to be processed by a variety of algorithms while keeping the user experience fast and smooth.

In this article I'm going to explain how we implemented cryptography in our web apps, how the W3C Web Cryptography API became our hero and why we needed to abandon it for some parts in favor of WebAssembly.

What E2E-encryption means for the code

To implement proper end-to-end encryption, a private key is always only kept locally on the user device it was generated on and is never transferred to any other party (most importantly, not to the server). Symmetric keys (i.e. as used for AES-256) are used to encrypt and decrypt data (e.g. files). These symmetric keys are then encrypted with the public keys of each trusted party (i.e. a user).

Typically, this general approach boils down to two kinds of cryptographic tasks that our applications need to be able to perform:

Transformation of data

When we want to apply a specific algorithm to some data and that data is already available to us, including all potential keys, we call this "transformation". There is just the single task of running the algorithm on the data.

For instance, our application could have obtained an AES key and a chunk of data (e.g. a file) already and we want to encrypt that data to receive some cipher text. Or, we have some plain text at hand which we want to hash to get a checksum.

Key generation

To be able to actually cryptographically transform data we often need some keys. To obtain a key it must be generated. As a general practice these keys should be generated and stored securely on the client device to minimize the risk of disclosure. For this, a client will need to generate cryptographically strong keys. Independent on the algorithm that the key is used for, this always boils down to generating random data with high entropy.

From Web Crypto to WebAssembly

Web Crypto: stepping into a worry-free bubble

Web apps in the browser, as of today, can't look back onto a long history of cryptographic applications. We were facing the question which browser APIs and/or libraries would allow us to fulfill all of these tasks, support our target systems (Firefox, Chrome, Safari, Edge and… IE11), be sufficiently fast and which would not require us to implement algorithms on our own (which you should never do).

So, when we discovered the W3C spec for the Web Cryptography API we were thrilled to see that things are shifting towards empowering web apps to fulfill cryptographically intensive use cases. The API supported the majority of the algorithms we needed.

Note that Web Crypto is not supported by IE11 and did not cover all functionalities we needed (e.g. parsing certificates). Therefore, we also included node-forge, a very exhaustive open-source implementation of cryptographic algorithms, to supply a fallback implementation. We'll leave these fallback implementations aside in this article.

We enjoyed using Web Crypto a lot. The API surface is small but powerful and the documentation comprehensible. Browsers produced very understandable errors if anything went wrong. The general API design is also very high-level, thus granting little room for control on how certain algorithms are being run. This drastically reduced the potential for shooting ourselves in the foot by doing something wrong.

Nevertheless, this high-level API design can also be the major downside of the the Web Cryptography API: It is a common ground for all developers to be able to implement cryptographic applications safely without digging into the very low-level details. Consequently for some more advanced uses cases, it offers too little control.

One of the core features of Bdrive is to also allow it to be used by institutions with strong legal requirements. Public services or health institutions, for instance, often cannot use software which did not obtain a certain certification. One of these is the Common Criteria evaluation (EAL 4+). In our case, this certification is performed by the Federal Office for Information Security (BSI). Among other things, this standard requires a certain level of entropy for generated cryptographic keys.

To prove that the app generates keys with such a high entropy the code needs to be manually reviewed. This not only refers to the code written by the neXenio engineers but also all included third-party software. Specifically, we need to make sure that Web Crypto API implementations in all browsers generate keys using a random number generator that provides at least 120 bits of entropy.

So, let's take a look at what the W3C spec has to say about entropy:

This specification provides no lower-bound on the information theoretic entropy present in cryptographically random values, but implementations should make a best effort to provide as much entropy as practicable.

https://www.w3.org/TR/WebCryptoAPI/#Crypto-description

If the spec does not require browser vendors to ensure a lower bound of entropy, could we maybe make sure that they do anyway? For instance, we can look at how Chromium-based browsers generate random values for cryptographic operations since the code is open source.

However, we couldn't do that for all of our target browsers (e.g. IE11 and Edge are closed-source). Also, implementations on Windows typically rely on RtlGenRandom() from the operating system's API which is also closed-source. As such it cannot be proven to provide the necessary level of entropy.

Overall, while the Web Cryptography API is very well suited to cryptographically transform data it is not capable of generating keys in a way that is compliant with our entropy requirements.

WebAssembly: leaving the bubble, entering the wild

As much as we loved working with the Web Crypto API, we needed a solution that gave us the control over what entropy sources are used while still having a performance that can compete with the Web Crypto API. We needed something that the browser did not offer. While there certainly were libraries for different languages that solved our use case, we were simply locked-out.

At that time, we already had a desktop client for Bdrive up and running for a while, written in C++. That desktop client faced the same entropy requirements. But being implemented in C++ we were free to pick a crypto library that supported this use case and went for Botan. Botan is a quite popular C++ library implementing a wide variety of low-level cryptographic algorithms. Moreover, Botan is open-sourced and has already been analyzed and approved by the BSI (German-only version of the analysis can be found here).

Lucky for us, WebAssembly (short: WASM) became available in all major browsers a short while ago (except IE11). WebAssembly is a portable instruction format that other languages (e.g. C++) can compile to and that can, for instance, be embedded and run inside a browser application's execution context.

WebAssembly code example as compiled by Chrome

For us this meant that we could actually make use of Botan inside of our JavaScript browser application by turning it into a WebAssembly module. That's why we decided to solve our issue of controlling the entropy of key generation by compiling the necessary parts of Botan to WASM.

To put it mildly, this idea was thrilling. Suddenly, if we are having an implementation challenge, we are no longer locked-in to what the JavaScript ecosystem and native APIs have to offer. Instead, the entire set of languages becomes available to use (well, more or less). Simply compile the code down to WebAssembly and put it into the browser. No more waiting for W3C API specifications to be proposed, discussed, tried out, standardized and adopted.

Is WebAssembly better than Web Crypto, then?

When having a code base that relies on WebAssembly to do some parts of cryptography and on the Web Crypto API for the other parts, one might ask if we couldn't just go for WebAssembly for all cryptographic operations. There are a couple of reasons preventing us from doing so:

Bundle size

A compiled WebAssembly file (.wasm) containing only those parts of Botan that allows us to source entropy, obtain random bytes and generate RSA keypairs is already 2.4 MB in size. A combination of code splitting and prefetching compensates a little for that. If Botan is compiled to WASM with all modules enabled though, the resulting .wasm file weighs about 13 MB. Even if we used it for all other algorithms as well and compiled those parts too, we would end up with a file that simply cannot be downloaded anymore in a reasonable time on slower connections. This would impair the user experience a lot and thus not align with our goal of making E2E-encryption easy.

Performance

We found the Web Cryptography API to have a better performance than our WASM-compiled version of Botan. For instance, we compared the performance in Chrome 73 on macOS when generating an RSA key pair with a modulus length of 4096 bits. Using the Web Crypto API will take 1.4 seconds on average for a single key pair. The same task takes 6.3 seconds on average when using Botan (as WASM).

To better visualize the overhead that calling into a WebAssembly module causes, we also compared the time it takes to generate 1024 random bytes 1024 times in a row. It took the Web Cryptography API in our Chrome (macOS) implementation just 6ms to generate those 65536 bytes. The implementation using WebAssembly took 46ms in average.

This immediately makes sense when considering that there is little overhead when calling into the Web Crypto API implementation of the browser. Moreover, this is exactly what the API can optimize for.

Nonetheless, it is likely that performance of WebAssembly execution will improve a lot in the future — specifically with upcoming features such as threads. Using features such as dynamic linking and improved tooling support, it is very imaginable that even the loading times due to large bundle sizes might decrease in the future.

Summing it up

We've been happy with the fusion of a WebAssembly-compiled Botan and the Web Crypto API for some months now. Naturally, using both at once is a compromise that comes with some downsides like added complexity or an increased bundle size. This compromise, however, allows us to enjoy the upsides of both solutions to the fullest. So let’s sum up where each of them shines:

Advantages of WebAssembly

  • very flexible since many libraries in different languages can be compiled and used (allowed us to meet our certification requirement)
  • will probably be used more often in the future

Advantages of W3C Web Cryptography API

  • faster than probably any WebAssembly-compiled implementation could be
  • no increase in bundle size since the platform (browser) includes the implementation
  • no need for additional build steps

Since it is unlikely that the Web Crpyto API will evolve to cover entropy level guarantees we'll likely keep the Botan/WASM solution. Since it's also unlikely that WebAssembly will reduce the downsides of performances and bundle size drastically in the near future, we'll also likely stick with the Web Crypto API wherever possible for a while.

However, it is exciting to see where things are moving. Maybe, in a couple of years, the need to use the native JavaScript APIs of the browser will vanish and instead, WebAssembly will be the platform default.

Are you interested in using or creating safer software with us? We are always looking for customers, partners and motivated engineers. Get in touch at nexenio.com.

--

--