"Nothing is as secure as a string phone" –The NSA

As you can probably tell from previous posts, I’ve been pretty into hardware lately. I’ve especially been building things like home sensors and controllers, so I have a central computer reading motion, temperature, humidity, light and other values in the house and deciding whether the lights or air conditioning need to be on or off.

I also want to be able to turn these on and off from my mobile phone, from anywhere in the world. The problem with that is that I need a way to ensure that only my phone can turn things on in my house. I wouldn’t want someone to be able to turn the heating on in my house at full blast when I’m not there and waste all my electricity bill (or set fire to something).

TLS is a pretty good solution, as it ensures confidentiality between client and server, but it does nothing for verifying the client or securing communications against a malicious server. I needed something better, and I couldn’t find something readily available. So I set out to write it. Thus, string phone was born.

String phone is a secure communications library and protocol, with a proof of concept/prototype written in Python. It’s still in the early alpha stages, but can be used for testing or non-production or non-critical environments.

String phone basics

The phone in question.

I wrote the initial version in Python, since it’s what I know best, and it’s simple enough to prototype in. I used the Python bindings to the excellent NaCl cryptography library, as it provides very solid primitives to expand on. I tried to do as little innovating as possible, and I pretty much just wrapped the NaCl functions in an API more suited to the use case I need. Python is a little heavy for embedded computing, but it’s already suitable for running on computers like the Raspberry Pi, BeagleBone, Yun, etc. The intention is that the library will be ported to other platforms later on.

String phone is basically an encryption layer for a pub/sub channel. A participant who wants to send a message to the channel encrypts it to a symmetric key that everyone in the channel shares, and signs it with their unique signing key. They then post it to the channel, where the recipients will receive it, validate the signature and decide whether they want to read it or not, based on who the sender is.

If the recipient decides to read the message, they can decrypt it with their topic key and read it. If not, they just discard it. String phone also provides a convenience parameter that will automatically discard messages from senders you don’t trust.

Simple API example

Here’s a trivial example. Alice and Bob here are the only two participants, and they decide to trust each other implicitly (setting the naive parameter to True).

# Generate a topic key to share.
>>> topic_key = stringphone.generate_topic_key()

# Instantiate the Alice and Bob objects.
>>> alice = stringphone.Topic(topic_key=topic_key)
>>> bob = stringphone.Topic(topic_key=topic_key)

# Encode a message to Bob.
>>> alice_message = alice.encode("Hi Bob!")

# Decode it in the naive mode, blindly trusting everyone.
>>> bob.decode(alice_message, naive=True)
'Hi Bob!'

API example with trust

Each participant has their own store of trusted keys, which they can add participants’ keys to, so strict mode decryption succeeds. Continuing the example above:

>>> bob.decode(alice_message)
# Try to decode the message in strict mode.
Traceback (most recent call last):
  File "stringphone/topic.py", line 166, in decode
UntrustedKeyError: Verification key for participant not found.

# Add Alice's public key to the keystore.
>>> bob.add_participant(alice.public_key)

# Strict mode will work now.
>>> bob.decode(alice_message)
'Hi Bob!'

Discovery example

You won’t always be able to generate and configure all the clients’ keys beforehand. String phone includes convenient helpers for discovering and trusting other devices. Here’s how that works:

# Alice will have the topic key, but Bob will have to discover it.
>>> alice = stringphone.Topic(topic_key=stringphone.generate_topic_key())
>>> bob = stringphone.Topic()

# Bob will introduce himself to the channel.
>>> intro = bob.construct_intro()  # intro should be sent as-is.

# Alice wraps the message in the Message convenience class and retrieves
# the sender's (Bob's) public key.
>>> message = stringphoneMessage(intro)
>>> message.sender_key
'\x0f\x83\xc7\xcb52\xe5,q\xba\xed\x94\xab...'

# Alice shows the user a prompt to ask them whether to trust Bob's key, and
# adds it to the store if the user says yes.
>>> alice.add_participant(message.sender_key)

# Construct the reply containing the topic key and send it to the channel.
>>> reply = alice.construct_reply(message)

# Bob will parse the reply, which will populate the topic with the
# topic key and return True to indicate success.
>>> bob.parse_reply(reply)

# Bob will ask the user to trust Alice's public key, and add her key if so.
>>> bob.add_participant(reply.sender_key)

# Now the participants can freely and securely talk to each other.
>>> message = bob.encode(b"Hey, Alice! Thanks for the key!")
>>> alice.decode(message)
'Hey, Alice! Thanks for the key!'

>>> message = alice.encode(b"Hey Bob! No problem!")
>>> bob.decode(message)
'Hey Bob! No problem!'

Epilogue

Following the short introduction above, we’ve already seen how we can securely and privately exchange authenticated messages with other participants, even over a completely insecure channel (for example, IRC or an MQTT orgweet server). The code is pretty simple to integrate, and the documentation is already rather extensive.

If you have any feedback or criticism, please leave a comment below or send me a Tweet. Talk to you later!