PROTECT – Why & How to build Client-Side Encryption in React.Js and beyond

What we’re building …. client-side encrypted content with ReAct.JS and SJCL.js

Problem Space

GMAIL, Facebook, Instagram, Dropbox… you name it… they typically store your data “unencrypted” or with a limited encryption where they control the keys. This means all your emails, pictures, messages and files are sitting on someone else’s computer for them to see. Even if they “encrypt” they still own your keys. Your data is always at risk of external attacker breaches such as Yahoo breach and Equifax breach and insider employees access breaching your your data. This type of design puts customer’s data at risk while Experian forecasts potential liability of trillions dollars lost a year globally. This articles demonstrates how to solve that problem and offers a more secure customer solution while reducing total liability to the business.

This article goes over the architecture to implement a client side hybrid encryption scheme in modern ReAct.js application using using industry standard algorithms to address this problem. My primary focus for this demo was for smaller JSON event based messaging systems like chat not file upload and download features.

Legal and Responsible Disclosure

Before we begin, I’d like to ask any readers to become educated and respectful of the federal and local laws regarding encryption, searches and seizures, key disclosures and freedom of speech. Various countries have import restrictions on cryptography, while other have laws regarding keys. Be aware of the proposed legislation, for example, the recent LEAD act (proposed) and EARN IT which would may put additional costs and liability on your system designs. Within the United State, a few items below are important to consider in system designs.

I also want to be up front and say, any encryption tool which can be used for good can be used for evil. A hammer can be used to build a home or as weapon. With great power comes great responsibility.

Simple Examples of Attack Vectors

Here is a simple example of a Cloud Employee browser access being abused to access an AWS DynamoDB database and viewing the customer’s data.

Here is a simple example of a Cloud Employee script access being abused to access an AWS DynamoDB database and viewing the customer’s data.

secsandman@ubuntu:~/Desktop/crypto-poc$ aws dynamodb scan --table-name notes
    "Items": [
            "attachment": {
                "NULL": true
            "content": {
                "S": "War is Peace\nFreedom is Slavery\nIgnorance is Strength "
            "createdAt": {
                "N": "1595899589245"
            "noteid": {
                "S": "5ca572d0-d071-11ea-bce7-51ecfaeff288"
            "userid": {
                "S": "us-east-2:00b46157-6a92-4f18-bedb-c286de5aa9ff"
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null

Your data compromised via Network Man In the Middle

Sometimes, vendors terminate Transport Layer Security at the perimeter of their product. If this is done then TLS and Browser mitigations will not help. Below is a demonstration that HTTP headers and content can be intercepted by abused network administrator access.

Out of respect for the companies and engineering teams, let me say that it’s not just “admins” or “employees”. Let me point out that I said “abused access” in the above statements. Meaning legitimate+good people’s daily access at work could be stolen or compromised and abused by someone else.

The Solution

The below application is essentially a serverless note taking app refactored to receive the user’s supplied input and posts the users data to AWS Lambda and dynamoDB. I’ve refactored the application to populate the cipher text payload in real time as the user types using Facebook FLUX Architecture. In reality, this feature is unnecessary client-side processing and is only provided to visualize the demonstration. Let’s check it out in action … each keystroke triggers an event that generates a JSON blob in the bottom TEXTAREA.

The Library

The application is built upon Stanford’s SJCL.js library. Per Stanford, “The Stanford Javascript Crypto Library is a project by the Stanford Computer Security Lab to build a secure, powerful, fast, small, easy-to-use, cross-browser library for cryptography in Javascript”. Standford researchers also found that when compared to must faster encryption algorithms (i.e. Salsa) against JavaScript AES there were no material performance benefits when implemented in browser. Although, this may have changed in recent years.

In 2010 whitepaper, SJCL was 11 times faster than comparable crypto libraries and 12% smaller. Currently, it has 55 contributors and nearly 10,000 users and only 1 medium vulnerability in the PRNG development branch. There may be more modern JavaScript libraries emerging, e.g. W3C, however they have more restrictive licensing while SJCL is a free software from a reputable source and includes algorithms which have no patent restrictions as well.

Architecture Components

Everything was tested on a x86 Linux virtual machine running approximately 6 GB of RAM in both a Chrome and Firefox browser. For native mobile, I can only guess that the AES 256-bit key size should be tested at 128-bit key size, the HEX encoding replaced with only bit arrays and the ElGamal should be tested against RSA. In addition, there will likely be a trade off between performance and security creating a random AES key for every message or every day or week or month etc. This last statement will affect the total N times the ECC function would be called to encrypt/decrypt a newly generated DEK, which is the most computationally intensive processes from my understanding.

Application Architecture Components

-- React.js
-- Node.js
-- Recompiled SJLC with ECC
-- React Boostrap
-- React Router

Key Storage and State

In this demo using a ReAct.js single page application (SPA) both key storage and state was the most difficult part to manage because I had to maintain the state of the asymmetric keys to encrypt and decrypt at a later date. As ReAct.JS teaches you that if you need to share state between sibling or lower level components you must “Lift your state up” into a shared parent. Much like a global variable. For the demo, I randomly generate the asymmetric keys within a top level component and used ReAct AppContext providers and the React Router to push the asymmetric AppKeys state down into the lower level functions. All asymmetric keys are stored within the application’s memory while the data encryption key is garabged collected after runtime and stored encrypted in dynamoDB. If, for any reason, the app refreshes then the keys are lost and you will be unable to decrypt the user’s test data. This was acceptable for my PoC but in practice a secure persistent storage for Asymmetric Keys managed by the end-user separately should come into play. I have some ideas on this that the industry is not capitalizing on.

The most important part of the production story, is the keys should be kept separately from the Vendor and the content is encrypted by the customer before putting the content in the Vendor’s product. Think of it like a customer renting safe (A) from SafeRus and putting their documents within a separate personal safe (B), keeping the keys at home and then putting their personal safe (B) inside the bigger rental safe (A) at SafeRus.

The Symmetric Encryption

Per OWASP best practice the demo application implements AES-256-bit key size encryption on the user supplied input. I saw no material impact in performance between 128-bit and 256-bit on small user message blocks. I did not test large file size however. I also considered that AES is a NIST standard and industry standard.

In order to use AES, you must first create an unpredictable AES encryption key. To achieve this the application introduces entropy into the system using a PRNG. The SJLC Pseudo Random Number Generator (PRNG) is a modified version of the Fortuna PRNG. Fortuna produces pseudorandom words by running a block cipher in counter mode. You can learn more about Stanford’s method of producing entropy here and in their whitepaper. the PRNG also requires seeding the buffer, otherwise the random() method is more likely predictable by attackers.

      var buf = crypto.randomBytes(1024 / 8); // 128 bytes
      buf = new Uint32Array(new Uint8Array(buf).buffer);
      sjcl.random.addEntropy(buf, 1024, "crypto.randomBytes");

To compute the symmetric data encryption key (DEK), the demo takes the 256-bit random- seed and feeds it into a SHA256 algorithm and serialize the SHA256 hash to HEX from bits. Although the HEX serialization may be unnecessary it assisted me in troubleshooting. I also found the serialization process assisted with various escaping that occurs across the network once the payloads hits my back-end infrastructure. In production, you may be able to remove this processing and improve performance with bit arrays.

const dekPlain = sjcl.random.randomWords(8);
const dekHashBits = sjcl.hash.sha256.hash(dekPlain);
const dekHashHex = sjcl.codec.hex.fromBits(dekHashBits);
var ciphertext = sjcl.encrypt(dekHashHex, UserInput, {"ks":256, "salt": SaltHashHex})

The Asymmetric Encryption

The application encrypts the data encryption key (DEK) using Elliptical Curve and El Gamal methods from SJCL. Elliptical Curves allow the client to save computational resources because the key sizes are much smaller. For example, a 256 ECC compared to a 2048 bit key size in traditional RSA with an equivalent strength. As a benchmark, the NSA recommended 384-bit ECC encryption for TOP-SECRET circa 2009 while NIST and other leading cryptographers have since deprecated RSA 1024 bit key sizes. I personally found that 384-bit key size was detrimental to user experience on a x86 architecture with 6 GB RAM, even with removing the event-based loop-backs into the ReAct.js view components. For the demonstration, I’ll be using 256-bit ECC key sizes which is greater than 2048 RSA.

The application implements the El Gamal crypto system. To put this in context, basically every modern crypto library uses the ECDSA standard as specified by NIST. The NIST Digital Signature Algorithm (DSA) was created from the El Gamal submission from the NSA and then re-introduced as a variant under the DSA acronym. Basically, we’re implementing similar cryptographic concepts as ECDSA.

Like the AES key, to generate an unpredictable asymmetric key pair you must first generate entropy from somewhere. The application introduces the same SJLC PRNG approach we mentioned earlier.

      // Generating entropy for the creation of Public/Private keys

      var buf = crypto.randomBytes(1024 / 8); // 128 bytes

      buf = new Uint32Array(new Uint8Array(buf).buffer);
      sjcl.random.addEntropy(buf, 1024, "crypto.randomBytes");

We then generate a pair of bit array public and private keys and serialize them into an hexadecimal codec. In practice, serializing is unnecessary when storing in a semi-structured object storage, however this additional processing is a convenience for troubleshooting when comparing inputs to outputs.

        var newPair = sjcl.ecc.elGamal.generateKeys(256);
        var publicKey =;
        var privateKey = newPair.sec.get();
        var publicKeyHex = sjcl.codec.hex.fromBits(publicKey.x.concat(publicKey.y));        

        var privateKeyHex = sjcl.codec.hex.fromBits(privateKey);

Here is a snapshot of the working demo where the Asymmetric keys are generated, passed down into the NewNote React.js component, the DEK is created, the DEK encrypts the users data and then the DEK gets encrypted with the public Asymmetric key. The encrypted blob is sent back to DynamoDB as a JSON object once the user presses “CREATE”.

Here’s a snapshot of the DynamoDB database where the user’s message content is not legible to the database administrator or any other employee for that matter. There is various meta-data available about the encryption used, the salt, the key size and the initialization vector. All these parameters are necessary to decrypt the data at a later date. The highlighted text is the encrypted data in HEX.

Decrypting the Data

Because this is a single page application (SPA), the user simply clicks the “message” that they want to view and the the React Router triggers a function that executes a GET requests against the AWS APIs to retrieve the encrypted message inside the SPA. At this point, the user’s private key is passed down to the “Notes” component and essentially stored in memory. The JSON in the top of the screen is the “encrypted blob” that was retrieved from DynamoDB.When the user presses “Enter”, a DOM event triggers an event function that decrypts the DEK with the private key and then decrypts the user message with the unencrypted DEK and renders the users message back to the browsers DOM. All this is happening within the users machine mind you.


As a side note, I only tested small JSON message blocks more suitable for instant messaging, forum boards and chat etc. etc..

Encrypting as a small message

Less than 200 ms, from the time I pressed the last keystroke in the sentence and the encryption function was called and returned it’s result back into the Application state to render in DOM. This includes various encoding and serialization that are unnecessary and rendering the cipher back to the page.

Decrypting a small message

Less than 70 ms, from the time I present “Enter” to the time the JSON object is parsed, the DEK is decrypted and the users message is decrypted with the plaintext DEK. Notably, ElGamal is known for fast decryption speeds.

And that’s it. Countless hours and late nights to write a web enabled client side hybrid crypto system in ReAct.Js.

A special thanks to Bipin Prabhakar at Indiana University from my master’s program who taught me these concepts nearly 8 years ago. RIP, Aaron Swartz who helped define the framework to share and protect my content under creative commons.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s