How to End-to-End Encrypt in Messages Using Twilio and Virgil Security

Rebecca YarbroughJanuary 25th, 2019

Many Twilio developers have used our existing end-to-end encryption Virgil SDK to secure message data in their products for compliance with HIPAA and GDPR, to limit developer liability from data breaches, to protect user privacy, and many other reasons.

With e3kit, a new client side SDK for Twilio, it’s now even easier to add end-to-end encryption to your Twilio product. e3kit is an end-to-end encryption SDK with a simpler set-up process, and it has features like multi-device support already built in.

In this post, we’ll walk you through how e3kit works and how you can start building end-to-end encryption into your Twilio messaging functionality.

If you have questions or want to see what other developers are building, hop on our Slack channel to join the conversation. We can’t wait to see what you encrypt!

What is end-to-end encryption?

This is what a typical Twilio app looks like today:

Without end-to-end encryption, the security gaps along the data path are where data breaches happen.

There are gaps in the encryption where the HTTPS and at-rest encryption starts and stops. Plaintext data is still accessible to developers and hackers on frontend and backend servers. Plus, governments, ISPs and telcos can see the data. Even if you trust all these people, if there is a technical mechanism that allows them to access it, that technical mechanism is available to anyone to exploit.

By default, Twilio Programmable Chat is already encrypted in transit with HTTPS. End-to-end encryption is an additional layer of security that can help companies satisfy regulations like HIPAA, GDPR, and the myriad of other security and privacy laws being passed by governments around the world. Regulators are paying more attention to privacy and passing laws demanding higher levels of security. Depending on the regulatory atmosphere of your industry or geography, HTTPS alone may not be sufficient.

Further, a two year study in the UK found that 88% of data breaches were caused by developer error, not cyberattacks. So if your data is not end-to-end encrypted, all it takes is a developer at some third party service with access to your data to make one implementation error or click through on a phishing email, and your whole database could be breached. End-to-end encryption is a layer on top that protects developers from both mistakes and hacks.

This is what your app will look like after you implement client-side end-to-end encryption:

With end-to-end encryption, there are no security gaps along the data path to expose your product to breaches.

The message data will be encrypted on the end devices and remain encrypted everywhere it’s sent and stored until the end user opens the message and decrypts it on her device. Neither you nor Twilio, nor any of the networks, servers, databases, or third party services will see anything but scrambled data passing through.

What can I end-to-end encrypt?

Anything – chat messages, files, photos, sensory data on IoT devices, permanent or temporary data. You decide what data you want to end-to-end encrypt — you can encrypt some fields in a document, but not others. For example, you might want to keep benign information related to a chat app (like timestamps) in plaintext but end-to-end encrypt the message content.

How do I implement it?

Below, we’ll provide you an overview of the implementation steps so you can get an idea for how it works. The full code is found on the Virgil Security dashboard once you create a Virgil Security developer account and follow the e3kit for Twilio end-to-end encryption guide within the dashboard.

Using the client-side e3kit SDK, when one of your users signs up, your app will generate a private and public key on their device. The user’s public key is published to the Virgil Cards Service (effectively a cloud directory that stores and manages the public keys) for users to find other users’ public keys and encrypt data for them. Each user’s private key remains on their device and is protected by the operating system’s native key store.

Step 1: Set up your backend

In order to identify and authenticate your users in the Virgil Cloud, you’ll need to generate Virgil and Twilio JWTs with the help of the Virgil SDK and Twilio Helper on your server side. We’ve created a sample backend code that demonstrates how to connect the Virgil and Twilio JWT generation. Check out the GitHub repo here and follow the instructions in README.

Step 2: Set up your client

e3kit is responsible for creating and storing the user’s private key on their device and for publishing the user’s corresponding public key in the Virgil Cloud. Everything else (except for the cryptographic functions) is handled by Twilio SDK, which you’ll have to initialize on the client side yourself.

A) Use the package manager to download the e3kit SDK to your mobile or web project.


npm install -S @virgilsecurity/e3kit

B) Initialize e3kit:

In order to interact with the Virgil and Twilio Cloud, the e3kit SDK must be provided with a callback that it will call to fetch the Virgil and Twilio JWT from your backend for the current user.


import { EThree } from '@virgilsecurity/e3kit';

// This function returns a token that will be used to authenticate requests
// to your backend.
// This is a simplified solution without any real protection, so here you need use your
// application authentication mechanism.
async function authenticate(identity) {
    const response = await fetch('http://localhost:3000/authenticate', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        body: JSON.stringify({
            identity: identity
    if (!response.ok) {
        throw new Error(`Error code: ${response.status} \nMessage: ${response.statusText}`);

    return response.json().then(data => data.authToken);

// Log in as `alice`
const eThreePromise = authenticate('alice').then(authToken => {
    // E3kit will call this callback function and wait for the Promise resolve.
    // When it receives Virgil JWT it can do authorized requests to Virgil Cloud.
    // E3kit uses the identity encoded in the JWT as the current user's identity.
    return EThree.initialize(getVirgilToken);

    // This function makes authenticated request to GET /virgil-jwt endpoint
    // The token it returns serves to make authenticated requests to Virgil Cloud
    async function getVirgilToken() {
        const response = await fetch('http://localhost:3000/virgil-jwt', {
            headers: {
                // We use bearer authorization, but you can use any other mechanism.
                // The point is only, this endpoint should be protected.
                Authorization: `Bearer ${authToken}`,
        if (!response.ok) {
            throw new Error(`Error code: ${response.status} \nMessage: ${response.statusText}`);

        // If request was successful we return Promise which will resolve with token string.
        return response.json().then(data => data.virgilToken);

// then you can get instance of EThree in that way:
eThreePromise.then(eThree => { /* eThree.encrypt/decrypt/lookupPublicKeys */})
// or
const eThree = await eThreePromise;

Step 3: Register your users with Virgil Security


// TODO: initialize

await eThree.register();

Step 4: Create a Twilio channel

Virgil doesn’t provide you with any functionality to create or manage users’ channels or messages. So, you’ll have to use the Twilio SDK to create a channel for user conversations.

Step 5: Sign and encrypt message data

In addition to encrypting message data for data security, e3kit uses digital signatures to verify data integrity.


// TODO: initialize and register user (see EThree.initialize and EThree.register)

// aliceUID and bobUID - strings with identities of users that receive message
const usersToEncryptTo = [aliceUID, bobUID];

// Lookup user public keys
const publicKeys = await eThree.lookupPublicKeys(usersToEncryptTo);

// Encrypt data using target user public keys
const encryptedData = await eThree.encrypt(new ArrayBuffer(), publicKeys);

// Encrypt text using target user public keys
const encryptedText = await eThree.encrypt('this text will be encrypted', publicKeys);

Step 6: Decrypt message and verify sender

After receiving a message, we’ll decrypt it using the recipient’s private key and verify that it came from the right sender by confirming that the message signature contains the sender’s public key.


// TODO: initialize SDK and register users - see EThree.initialize and EThree.register

// bobUID - string with sender identity
// Lookup origin user public keys
const publicKey = await eThree.lookupPublicKeys(bobUID);

// Decrypt data and verify if it was really written by Bob
const decryptedData = await eThree.decrypt(encryptedData, publicKey);

// Decrypt text and verify if it was really written by Bob
const decryptedText = await eThree.decrypt(encryptedText, publicKey);

Voila! You’ve end-to-end encrypted messaging in your app.

As we mentioned, Virgil e3Kit SDK supports multi-device support. You can find instructions for implementing that here, as well as support for additional functionalities like password changes and device cleanup.

Any Drawbacks?

The good news is that there is no backdoor to this technology, and no one except the private key holders will be able to access encrypted data. And sometimes the bad news is that there is no backdoor to this technology, and no one except the private key holders will be able to access encrypted data…

  • At the same time, encrypting end-to-end may remove the ability to access advanced functionality such as searching chat history. Before implementing additional security you will want to evaluate if it will address your business needs. (Just like users have private keys to access data, you can give admins a private key as well, but you need to do so carefully and be aware of how that impacts compliance with HIPAA and other regulations.)
  • Similarly, third party services won’t be able to do much with data that you’ve encrypted. If there’s data that you’re going to want to run analytics on or access for another business purpose, consider leaving that part of the data unencrypted.
  • There’s a minor performance hit involved in encrypting and decrypting data. Something along the lines of 1-2 ms per message on the client device. Plus, your clients will need network access whenever they want to encrypt that message (user key lookup is an online operation – which you can cache after it’s done).

To get started with e3kit for Twilio, sign up for a free Virgil Security developer account at, create your first application and follow the quickstart guide for end-to-end encryption with e3kit for Twilio. The e3kit documentation can also be found directly via

Questions? Feel free to ask the Virgil Security team on Slack.

e3kit: A New SDK to Make End-to-End Encryption Even Easier
Rebecca YarbroughJanuary 16th, 2019