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

Rebecca YarbroughJuly 8th, 2019

With the Virgil Security E3Kit SDK, developers can add end-to-end encryption to PubNub Chat to secure communication between users and devices.

Once E3Kit is implemented in your application, message data will be encrypted before it is sent to PubNub, and then only decrypted by the recipient. It’s a great tool for developers who want to encrypt message data in healthcare and financial services products, protect IoT command data and verify firmware updates, and build secure messaging between multiple users or devices.

Features Supported by E3Kit

  • access to encrypted data from multiple devices
  • data recovery in case of a lost or damaged device
  • group messaging with the ability to give new members access to message history and revoke message access when members are removed
  • compatibility across devices

Using the E3Kit SDKs for iOS, Android and JavaScript, 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.

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.

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 the encryption works

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 E3Kkit 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
use_frameworks!

pod 'VirgilE3Kit', '~> 0.5'
end

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)
           return
   }

   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.

Next
September Product and Customer Updates
Rebecca YarbroughSeptember 24th, 2019