Designing Secure Group Chat in Peerio: Peerio
January 12, 2018

Designing Secure Group Chat in Peerio

Last week, researchers from the Ruhr University Bochum released a paper revealing weaknesses in WhatsApp’s security design. Specifically, they discovered that WhatsApp’s servers have complete control over the user list in a group chat, meaning a malicious server could add members to the group. This effectively defeats the goals of end-to-end encryption, as anyone who’s able to access the servers — attackers, WhatsApp staff, or authorities able to legally prompt action from WhatsApp — could discreetly add whoever they want to an existing group.

Working in security certainly has its stresses, and despite having undergone multiple third party audits and regularly reviewing our own work, this news prompted us to do an internal review of our own group chat design to ensure we couldn’t find any such weaknesses.

Peerio’s top priorities are security and usability, and our everyday challenge is to balance both, always improving one without harming the other.

Group chats are one of the more complex points that we had to deal with, and today we are going to talk about their security design. What exactly makes Peerio group chats flexible enough to implement almost any feature you might find in a non-end-to-end encrypted chat app? How can you safely design an encrypted chat application while providing the kind of features that centralized servers are good for? How does Peerio do this in a way that isn’t subject to problems like WhatsApp?

Before we started designing the architecture for group chats we made a list of features we knew we’d need to implement sooner or later. Some of these are security features, other are simply nice things we know users expect. The list includes things like:

  • The ability for an admin to add and remove members from the group
  • A way to assign role-based privileges to individual users, giving different people different rights and actions they can perform in the group
  • A way for the group manager to decide whether new members would be able to read older messages; provided by cryptography as opposed to server logic
  • Allow a user to decide if they actually want to join the group by having them receive an invitation before being added to the group
  • Various kinds of notifications for invitations, role assignments, etc.
  • Clear visibility of who has what role and privileges in the group chat
  • Protection against a malicious server, so your data is safe even if our servers were compromised

A brief background on Peerio’s architecture.

We weren’t adding a group chat feature to Peerio in vacuum, there’s our powerful KegDB protocol/system that we had implemented, you can read all about it here. The TL:DR version is that KegDB is a system that allows Peerio clients to treat the server as a kind of encrypted database. Every database has a key that is known only to the user(s) who’s supposed to have access to it. This system has plenty of features built in, such as;

  • Signing and signature verification, to ensure the correct people, not imposters, are sending and receiving data
  • Data ordering protection, to ensure your messages and files are received in the order they were sent in
  • Support for multiple clients of the same user connected at the same time, so you can be logged into your Peerio account on your phone and computer at the same time
  • Sessions support, so you can go offline and resume data sync from where you left of
  • Much, much, more, it’s basically what enables Peerio to function as an encryption platform rather than a simple messenger

Every Peerio user has their own KegDB database to store encrypted information for themselves. There are also direct message (DM) Keg databases meant to be shared between any two users to exchange encrypted data strictly between them.

Making room for groups

For managed group chats we introduced “rooms” and a new kind of Keg database to go with it — “room kegDBs”.

Every room kegDB contains a record we call the Boot Keg. The Boot Keg’s role is to provide room members with keys to encrypt and decrypt data in this room (and only this room), and to provide the server with the list of members so that the server can deliver and optimize notifications.

Boot keg structure, slightly simplified:

 {
      roles: {
        admin: ["username", "username1", ...]
      },

      encryptedKeys: {

         0: {
               keys: {
                        username: {
                          key: b64 encoded buffer
                        },
                        username1: {...}
                 }
         },
         1: {...}
      }
  }

We also added a separate server API to handle invitation to rooms, as we wanted to give users an option to decide whether or not they wanted to actually join a room (anyone who gets added to all sorts of terribly boring group threads probably understand why)

Room lifecycle

Creation

When you create a room in Peerio, here’s what happens under the hood:

  • The server will create a new KegDB of the ‘room’ type and consider you the only person who can write a first version of a Boot Keg to that room.
  • Your client will generate a new random symmetric key for the room (known as the room key).
  • Your client encrypts the room key asymmetrically for your own public key with a new random key pair.
  • Your username gets added to the ‘admin’ role user list
  • All of this is then stored in the Boot Keg on the server.

Adding members

When you want to add a member to your room (you have to be an admin of the room to do so), the following things happen:

  • Your client encrypts the room key asymmetrically for the user you are inviting to the room.
  • Your client stores that encrypted room key in the Boot Keg and saves it.
  • The server detects that a new member has been added to the Boot Keg and generates an invite.

Cryptographically speaking, at this point the new member already has access to your room data, meaning they’re able to decrypt the room’s key and then the data in that room, but our server doesn’t start sending data and notifications until they accept the invite. Other participants can see the new member from the Boot Keg and invite status from a separate API. Needless to say, the Boot Keg is signed by the admin who changed it last.

Removing members

When an admin removes a member from a room, they simply delete the record in the Boot Keg that contains the room key encrypted for that user. Other participants and the server will see this change to the Boot Keg, and the server stops providing access to the room data for the deleted member. Furthermore, the admin generates a new key for the room and from that moment every participant uses the new key to prevent a malicious server from sending new messages to the former member, who could conceivably decrypt them with the old key. There are some other less important lifecycle steps that we will skip for brevity.

How Peerio protects your group chats.

The first important step is Trust On First Use (TOFU). Your client stores encrypted information about your contacts so when something changes (like a malicious server pretending to be your contact) your client will warn you and provide a way to verify your contact’s identity out of band. Second is cryptographic signatures. Any important piece of data going to and from your contacts is signed. Your client verifies your signature when decrypting data and verifies the signer over TOFU, because we don’t want just any signature.

Designing for the worst case scenarios

Our team has quite a bit of expertise and skill, but when it comes to security, you should always consider the absolute worst case scenarios. As awesome we can be, we’re also realists who recognize the threat an army of 10,000 hackers sponsored by a well funded state actor poses to a team of 20 like ours. As such, we always develop our apps with zero trust to our own server to provide the best-worst case scenario we can.

If it’s not clear already, we can’t read your encrypted data, and neither can this 10,000 strong hacker army. So what if this army did manage to compromise our servers?

Well, in a scenario where something goes awry and our server is compromised, the only damage the server can do is to remove data or prevent communication — an issue any centralized server will face.

There’s also a possibility of ex-participants, in collaboration with the malicious server, regaining access to data they previously had access to. So someone would have to have been in a group chat, collaborate with someone who can intentionally corrupt the server, and take advantage of that data being removed and rolled back so they can regain access to a group they had previously had access to.

That’s about it. Of course, we also undergo regular audits every time we make significant changes or add new features, just to get an extra set of eyes on the problem. All our client code is also open source, so if you’re so inclined, take a peek for yourself.