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

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

Dmytro Matviiv — September 22nd, 2020

Many Twilio developers have used our end-to-end encryption E3Kit 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 client-side SDK for end-to-end encrypting any data, it’s possible to add end-to-end encryption to your Twilio Programmable Chat product. E3Kit is an end-to-end encryption SDK with features like multi-device support and group chat built in. The best part is that you don't need to be an encryption expert to use it.

In this technical tutorial, we’ll walk you through how E3Kit works and how you can start building end-to-end encryption into your Twilio chat app. For more information about compliance with HIPAA and how Twilio and Virgil interact, see our overview here.

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.

Note: These code snippets are in Javascript, but E3Kit works across any language. You can find snippets for Java, Kotlin and Swift in the documentation here.

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

To find the preferred installation package use the following link: install Virgil E3Kit guide

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)

/// alice and bob - recipient identities
// steve - sender identity
const identities = ['alice', 'bob'];

// Find users cards with public keys
const findUsersResult = await eThree.findUsers(identities);

// Encrypt text string
const encryptedText = await eThree.authEncrypt('message', findUsersResult);

// Encrypt encoded data
const encryptedData1 = await eThree.authEncrypt(
  { value: 'bWVzc2FnZQ==', encoding: 'base64' },

// Encrypt binary data
const encryptedData2 = await eThree.authEncrypt(new Uint8Array(), findUsersResult);

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

// steve - sender identity
const sender = 'steve';

// Get sender card with public key
const senderCard = await eThree.findUsers(sender);

// Decrypt text and ensure it was written by sender
const decryptedText = await eThree.authDecrypt(encryptedText, senderCard);

// Decrypt encoded data and ensure it was written by sender
const decryptedData1 = await eThree.authDecrypt({ value: encryptedText, encoding: 'base64' }, senderCard);

// Decrypt binary data and ensure it was written by sender
const decryptedData2 = await eThree.authDecrypt(encryptedData2, senderCard);

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.

HIPAA Compliance Considerations

If you're adding E3Kit to your healthcare application and need to be compliant with HIPAA's requirements, you'll need to delete message data from Twilio upon message delivery. You can read more about building a HIPAA-compliant chat app with Twilio and Virgil Security in our guide here.

Any Drawbacks?

  1. 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.)
  2. 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.
  3. 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.

How to build a HIPAA-compliant Firebase Chat using security SDK
Dmytro Matviiv — August 11th, 2020
New End-to-End Encryption Features for Groups and more
Dmytro Matviiv — September 22nd, 2021