End-to-End Encrypted Messaging with PubNub and Virgil Security

Rebecca YarbroughJuly 8th, 2019

End-to-end encryption gives developers the ability to verify the identities of users or devices and to precisely control access to data moving through your network.

But key management to enable this end-to-end encryption across devices and users can be complicated.

With Virgil Security, functions like generating private keys on the end device, managing the public key directory, and implementing a sign-and-verify step for all encrypted communications are available through open source SDKs.

Using these server- and client-side SDKs, developers can access Virgil’s cloud-based key management service paired with our state-of-the-art open source crypto library, complete with elliptic curve keys 200x harder to break than RSA.

In this tutorial, we’ll show you how to implement Virgil’s e3kit SDK to add an end-to-end encryption layer to your PubNub communications for a multitude of use cases, including:

  • Encrypting and verifying firmware updates
  • Asymmetric end-to-end encryption to secure data at all times
  • Group messaging between users or devices
  • Access to encrypted data from multiple devices
  • Data recovery in case of lost or damaged device

Why end-to-end encryption?

End-to-end encryption is a fail-safe way to protect data and devices from both attacks and human error.

The leading cause of most data breaches is not actually caused by some hacker hunched over a laptop, but actually by developer error.  “Industry researchers predict that 80 percent of cloud data breaches will be due to customer misconfiguration, mismanaged credentials or insider theft, rather than cloud provider vulnerabilities by 2020” according to Data Breach Today.

When you encrypt data on the client device, neither the cloud (aka data center) nor you the developer have access to the decryption keys, which means if a breach happens, only scrambled gibberish will be found.

For example, commands being sent from a user’s iPhone to their thermostat to program “vacation mode” (which could indicate a vacant and vulnerable home) or messages from a sensor showing manufacturing facility metrics (which could expose confidential corporate information) would be protected, and only accessible with the private key stored locally on the authorized device.

How does end-to-end encryption work?

Let’s use a simple chat as an example. When you type in a chat message, it is encrypted on your mobile device or in your browser and decrypted only when your chat partner pulls up the message in her chat window.

With end-to-end encryption, the message remains encrypted:

  • While it travels over Wi-Fi and the Internet
  • Through your cloud data center’s web servers and backend servers
  • In the database
  • Through other servers en route to your chat partner’s mobile device

In contrast, with HTTPS:

  • Data is only encrypted as it goes over WiFi and the Internet, but unencrypted elsewhere.
  • Your cloud vendor, data center, app developers, etc. can access your users’ plaintext chat messages on every server and in every database involved.

When cloud service providers add an extra layer of security by using “at rest” encryption, it typically means that the database file is encrypted on disk with a key that a cloud service provider can access. Hackers can capture that key and hack into the web or backend servers and capture all the data that’s going through them, or can hack into the live database and breach them out.

Private & public keys

The underlying technology in end-to-end encryption is based on private and public keys:

Virgil Security Key Management System

Each user in the system has a public & private key pair:

  • The public key is shared with all users in the system, almost like a phone number in a telephone directory. Anyone who wants to send you a message can look up your public key and encrypt a message that can only be decrypted by your corresponding private key.
  • Your private key is secret to you and is used to decrypt data that has been encrypted for you. If a public key is like a phone number in a telephone directory, the private key is like a PIN to access any messages sent to that number.

Virgil Security’s SDKs and cloud service create the keys, share the public keys between users, and keep the private keys safe so that only the end user has access to it on their device. Neither Virgil Security nor PubNub have access to the private keys.

Step 1: Set up your backend

You’ll need at least a basic backend server for your app that has user authentication. Then you’ll use one of these sample apps to set up the Virgil SDK on your backend to generate JWTs for each of your application’s users that will identify them and grant them access to the Virgil Cloud – Node.js | Golang | PHP | Java and follow the instructions in README.

Step 2: Set up your client

On the client side, we will use the Virgil e3kit SDK to create and store the user’s private key on their device and to publish the appropriate public key in the Virgil Cloud.

You’ll need to install and initialize e3kit SDK in this step.

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

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

To install e3kit in your iOS project, if you haven’t yet, you’ll need to install CocoaPods:

$ gem install cocoapods

If CocoaPods is not yet integrated in your iOS project, you’ll need to initialize CocoaPods:

$ pod init

To integrate Virgil E3Kit into your Xcode project using CocoaPods, you’ll need to specify it in your Podfile:

target '<Your Target Name>' do

pod 'VirgilE3Kit', '~> 0.5'

Then, run the following command to finish the integration:

$ pod install

B) Initialize e3kit:

In order to have an eThree instance and interact with the Virgil Cloud, e3kit must be provided with a callback that it will use to fetch the Virgil JWT from your backend for the current user.

import VirgilE3Kit
import VirgilSDK

let connection = HttpConnection()

// 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.
let authCallback = { () -> String in
   let url = URL(string: "http://localhost:3000/authenticate")!
   let headers = ["Content-Type": "application/json"]
   let params = ["identity": identity]
   let requestBody = try! JSONSerialization.data(withJSONObject: params,
                                                 options: [])

   let request = Request(url: url, method: .post,
                         headers: headers, body: requestBody)
   let resonse = try! connection.send(request)

   let json = try! JSONSerialization.jsonObject(with: resonse.body!,
                                                options: []) as! [String: Any]
   let authToken = json["authToken"] as! String

   return authToken

let authToken = authCallback()

let url = URL(string: "http://localhost:3000/virgil-jwt")!
// We use bearer authorization, but you can use any other mechanism.
// The point is only, this endpoint should be protected.
let headers = ["Content-Type": "application/json",
              "Authorization": "Bearer " + authToken]

// This function makes authenticated request to GET /virgil-jwt endpoint
// The token it returns serves to make authenticated requests to Virgil Cloud
let tokenCallback: EThree.RenewJwtCallback = { completion in
   let request = Request(url: url, method: .get, headers: headers)

   guard let response = try? connection.send(request),
       let body = response.body,
       let json = try? JSONSerialization.jsonObject(with: body, options: []) as? [String: Any],
       let jwtString = json?["virgilToken"] as? String else {
           completion(nil, AppError.gettingJwtFailed)

   completion(jwtString, nil)

// Initialize EThree SDK with get token callback to your sever
// E3kit uses the identity encoded in the JWT as the current user's identity
EThree.initialize(tokenCallback: tokenCallback) { eThree, error in
   // Init done!


Step 3: Register your users with Virgil Security

The eThree.register() method checks whether a user already has a private key saved in local storage and a published public key on Virgil Cloud, and, if the user doesn’t have them, the function generates a new keypair for the user, saves the private key locally and publishes the public key on Virgil Cloud (for other users to reference).

To register users on Virgil Cloud you have to use eThree.register() method during the Sign Up flow in your application:

import VirgilE3Kit

// TODO: Initialize E3Kit - see EThree.initialize for the full sample

// Generates new keypair for the user. Saves private key to the device and
// publishes public key to the Virgil's cloud

eThree.register { error in
   guard error == nil else {
       // Error handling here
   // User private key loaded, ready to end-to-end encrypt!


Step 4: Encrypt message data

Before sending your message through PubNub, you need the recipient’s public key to encrypt that message with. In addition to encrypting message data for data security, e3kit uses digital signatures to verify data integrity.

import VirgilE3Kit

// TODO: init and register user (see EThree.initialize and EThree.register)
// TODO: Get users UIDs

let usersToEncryptTo = [user1UID, user2UID, user3UID]

// Lookup user public keys
eThree.lookupPublicKeys(of: usersToEncryptTo) { lookupResult, error in
   guard let lookupResult = lookupResult, error == nil else {
       // Error handling here

   // Load data to memory
   // ...
   // Encrypt data using target user public keys

   let encryptedData = try! eThree.encrypt(data: data, for: lookupResult)

   // Encrypt text using target user public keys
   let encryptedText = try! eThree.encrypt(text: "Hello, team", for: lookupResult)

Step 5: Decrypt Message and Verify Sender

After receiving a message from PubNub, 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.

import VirgilE3Kit

// TODO: init SDK and register users - see EThree.initialize and EThree.register
// TODO: Get user UID

// Lookup user public key
eThree.lookupPublicKeys(of: [bobUID]) { lookupResult, error in
   guard let lookupResult = lookupResult, error == nil else {
       // Error handling here

   // Decrypt data and verify if it was really written by Bob
   let originData = try! eThree.decrypt(data: encryptedData, from: lookupResult[bobUID])

   // Decrypt text and verify if it was really written by Bob
   let originText = try! eThree.decrypt(text: encryptedText, from: lookupResult[bobUID])


Next Steps

Virgil Security give you access to powerful, tested elliptic curve technology in simple SDKs that talk between applications and devices. And, with the cloud-based key management service, you no longer have to find a place to hide the decryption key yourself.

The SDKs are compatible across all client platforms (iOS, Android, web, server and IoT) and uses. It’s the ultimate security configuration that complements the other security mechanisms that PubNub already has in place. Your customers (and their CISOs) will thank you.

Virgil Security, Inc. enables developers to eliminate passwords & encrypt everything, in hours, without having to become security experts. Get started today at VirgilSecurity.com.