How to Implement Cryptographic Command Verification in Your IoT Smart Door Lock

This article was originally published on the Virgil Security blog. Virgil Security, Inc. enables developers to encrypt everything without having to become security experts. 

Around the globe, tens of thousands of new IoT devices will come online in the time it takes to read this article. But if there’s anything that’s going to hold back the future of IoT, it’ll be security. While consumers might tolerate the occasional data breach online, there will be no margin for error when it comes to life or death systems like those powering autonomous vehicles, traffic systems or even smart door locks.

So where do developers start? With IoT, the critical components are less the “things” and more the data being collected and consumed by those things. The first step to protecting the data layer is verifying that the data being sent to and from an IoT device has not been manipulated or altered by another party.

In this tutorial, we’ll apply this concept to two real-world scenarios involving a smart door lock:

  1. Building a cloud-connected smart door lock in a way that prevents it from being opened by the manufacturer or by anyone with access to the manufacturer’s cloud account.
  2. Preventing thefts of opportunity by preventing the manufacturer’s cloud backend from revealing which doors are open and closed.

For the tutorial, we’ll use PubNub’s Data Stream Network for message delivery, Virgil Security’s open source SDK for data verification and Virgil’s key management service for securely exchanging keys for verification and end-to-end encryption.

How can IoT devices be compromised and why does it matter?

By their very nature, IoT devices can be very small, and common security mechanisms simply can’t fit on board. And unlike your smart phone, which you expect to update regularly, most people don’t expect to update their smart toaster or router. This is problematic from a security standpoint because it makes the device vulnerable from the get to, and also hard to update once vulnerabilities are discovered.

Let’s look at how easy it is to trick an IoT device into accepting a command from an unauthorized party.

As you know, almost every kind of electronic device will need a firmware update at some point. How will the device know that the firmware that just came down from the server is safe to run? By using the device manufacturer’s public key to verify who signed the package. This is standard practice for all installer packages and apps deployed from app stores.

What if we tweaked the above use-case for a smart door lock that wants to verify if an OPEN command sent to it has actually been initiated by a user who’s allowed to open the door? It’s the same use-case, but a problem that isn’t widely solved in IoT products today for a variety of reasons including the financial costs of implementing previously available security technology and lack of consumer pressure. The end-result is a market full of insecure IoT devices – hackable pacemakerssmart cars that can be taken over, and millions of doors that can be opened by anybody who takes over the door lock manufacturer’s cloud account.

As we see here in this illustration, without encryption or signing, it’s quite simple for an unauthorized party to read commands going to and from a door lock and even send commands or firmware.

Typically we would be able to verify the identity of the sender by using the door lock’s public key, but in reality, IoT devices aren’t built with keys planted on the device. That’s what we’re changing, now that the technology exists. Keep reading and you’ll find how you can implement the same authenticity check for your IoT product that we use to verify firmware packages today.

How does data verification work?

When Alice wants to open her door, here’s how the lock determines that it’s Alice trying to get through, and not an intruder:

  1. Alice’s mobile app uses Alice’s unique, device-stored private key to sign the OPENDOOR command.
  2. The resulting Base64 string is then sent up to the door manufacturer’s cloud, which queues the command in a database.
  3. The WiFi-connected door lock de-queues the latest command from the manufacturer’s server and receives the command, signed by Alice.
  4. The lock verifies if the Base64 string has been really signed by Alice using her cloud-stored public key.
  5. If verification succeeds, the lock opens.

In the illustration below, we see this in action.

A private & public key pair for every user

As you might have guessed, in this system every user has a private key in the app and a public key in the cloud, so that the door lock can verify that the OPENDOOR command has indeed been issued by the appropriate user.

That’s where Virgil Security’s Cards Service comes into play. Using Virgil’s e3kit SDK in Swift, you’ll bootstrap Alice with a new private key on her iPhone and a new public key in Virgil’s cloud:

Swift code running on Alice’s iPhone:
import VirgilE3Kit
// URL to get Virgil JWT token
let jwtEndpointURL = URL(string: "https://your_backend/api/generate_jwt")!
// TODO: Authenticate Alice in your app
// After you successfully authenticate Alice, retrieve her Virgil JWT from your backend
let tokenCallback: EThree.RenewJwtCallback = { completion in … }
// Now, let’s init Virgil’s e3kit SDK with her JWT
EThree.initialize(tokenCallback: tokenCallback) { eThree, error in
  /* Bootstrap (authenticated) Alice: this call will create a new private key for her if
     she doesn’t have one yet and will publish her public key in Virgil’s cloud where the smart lock can look it up later for command verification. */  eThree.bootstrap(password: password) { error in … }
}

Sign and encrypt the OPENDOOR command

Then, when Alice wants to open her door, we’ll sign and then encrypt the command on her device.

Why do we encrypt the command and not just sign it? We want to prevent the door lock manufacturer from being be able to tell whether the command that’s been just issued is OPENDOOR or CLOSEDOOR. Only the door lock should be able to decrypt the command with its local private key. Because if the manufacturer knows which doors are open at any given moment, that means you then have to trust them to protect that information on your users’ behalf. Just imagine what someone could do if they knew which doors in a city or at a sensitive location were unlocked at any given moment. Instead of relying on third parties like the manufacturer to prevent a potential robbery (or worse), encrypt the command on the device and prevent it yourself. Remember, widespread cyberattack via vulnerable IoT devices and/or manufacturer is not a hypothetical outcome.

Here’s how to implement signing and encryption on the user’s device:

Swift code running on Alice’s iPhone
import VirgilE3Kit
import PubNub
let signAndEncryptFor = ["DoorLock123@VirgilDoorLock.io", “Alice@VirgilDoorLock.io”]
// Lookup public keys
eThree.lookupPublicKeys(of: signAndEncryptFor) { foundKeys, errors in
  guard errors.isEmpty else {
      // Error handling here
  }
  // Sign and Encrypt message to door lock and to myself
  let encryptedCmd = try! eThree.encrypt(text: "OPENDOOR", for: foundKeys)
}
// Initialize and configure PubNub instance
let configuration = PNConfiguration(publishKey: "demo", subscribeKey: "demo")
let client = PubNub.clientWithConfiguration(configuration)
// Send command from the user’s iPhone over to the door lock device with PubNub
client.publish(encryptedCmd, toChannel: "DoorLock123",
              compressed: false, withCompletion: { (status) in … })

Private & public key pair for every door lock

To decrypt the commands sent to it, a door lock also needs a private key on the device and a public key in the cloud.

The following code snippet shows how Alice’s brand new door lock will get its own private key when she installs it out of the box. This piece is written in C++ for the door lock IoT device:

C++ “open box” code running on door lock IoT device:
#include <virgil/sdk/cards/CardManager.h>
#include <virgil/sdk/jwt/providers/CachingJwtProvider.h>
#include <virgil/sdk/cards/verification/VirgilCardVerifier.h>
using virgil::sdk::crypto::Crypto;
using virgil::sdk::cards::CardManager;
using virgil::sdk::jwt::CachingJwtProvider;
using virgil::sdk::cards::verification::VirgilCardVerifier;
// Initialize crypto instance
auto crypto = std::make_shared<Crypto>();
// Generate key pair with private key
auto keyPair = crypto->generateKeyPair();
// Retrieve JWT from your backend
auto renewJwtCallback = [&](const TokenContext& context) { … }
auto provider = std::make_shared<CachingJwtProvider>(renewJwtCallback);
auto verifier = std::make_shared<VirgilCardVerifier>(crypto);
auto cardManager = CardManager(crypto, provider, verifier);
// Publish card on Virgil’s Cards Service
auto future = cardManager.publishCard(keyPair.privateKey(), keyPair.publicKey());
auto card = future.get();

Smart door lock receives OPENDOOR command

Now that Alice is set up with private and public keys, and the door lock is also set up with its own private and public key, we can finally implement the decryption and verification of commands on the door lock device.

C++ command listener code running on door lock IoT device:
#include "pubnub.hpp"
// Search for Public Key of users who are permitted to open Alice’s door    
auto future = cardManager.searchCards("Alice@VirgilDoorLock.io");
auto cards = future.get();
auto publicKey = cards.front().publicKey();
// Init PubNub
pubnub::context pb("demo", "demo");
// Subscribe to the "door lock’s own" channel then process the result
pb.subscribe("DoorLock123").then([&](pubnub::context &pn, pubnub_res res) {
   if (PNR_OK == res) {
       auto msg = pn.get_all();
       
       for (auto &&m: msg) {
           auto data = VirgilBase64::decode(m);
           auto decryptedCmd = crypto->decryptThenVerify(data,
                                                         privateKey,
                                                         publicKey);
           // Process decrypted and verified command
       }
   }
}

Get Started

Now you know how to use cryptography to build a IoT device so that it can only be controlled by a verified user and only user and the device can see the signals going back and forth between them.

As of 2017, the number of IoT devices on the planet now outnumber the number of people. And that number will only increase. But the IoT industry should learn from the mistakes of the Internet boom and bake security into products from the beginning. Not only will it save money and time, the stakes are high enough with IoT devices that it might also save lives.

To build data verification into your own product, follow these simple steps:

  1. Sign up for a Virgil account
  2. Use the e3kit SDK (still in beta) to implement the above features in your mobile/web apps. (Note: for now, the beta e3kit SDK relies on Google Firebase’s auth to authenticate your users. We’ll be releasing updates soon to support compatibility with any auth.)
  3. Use the Virgil C++ SDK to implement the features on your IoT board. Hook up the Virgil C++ SDK to your Firebase backend, so that it connects to the same key management service as E3kit in your mobile app.
  4. And if you don’t have one already, sign up for a PubNub account to implement realtime messaging across your devices.

The Virgil Security team is extremely active on Slack. Slack us with any questions, comments or feedback!

Try PubNub Today