April 2020 Update: End-to-end encryption is now officially out of beta, click here for more info.
Messages you send via Pusher Channels have always been encrypted in transit to and from Pusher. But until recently, there wasn’t end-to-end encryption. With our new end-to-end encryption feature, you can add end-to-end encryption to your application with one line in your server configuration!
This means you can prevent Pusher from reading all sensitive content (such as medical or financial data).
How do I use end-to-end encryption?
Let’s implement end-to-end encryption in that “doctor’s office” app. In this example, we have a web app for your users (doctors), and a server written in PHP.
Our end-to-end encryption feature is implemented on a new channel type, which begins with
private-encrypted-. For example, the doctor with ID 42 in your system might have the channel
private-encrypted-dr-42. When your server learns of a new appointment request, it will trigger an event on that encrypted channel:
'private-encrypted-dr-' . $_POST['doctor-id'], // an encrypted channel
array("patient-id" => $_POST['patient-id'], "category" => $_POST['category'])
Notice that the body of this event contains sensitive health information: the “category” of the appointment, such as “anxiety”. But by switching to
private-encrypted-, Pusher is unable to see this information, because the
$pusher->trigger function will encrypt the body. To do so, the
$pusher object needs a master key. You provide this master key when initializing the library:
$pusher = new Pusher\Pusher(
'cluster' => getenv('CHANNELS_APP_CLUSTER'),
'useTLS' => true,
'encryption_master_key' => '2tPOaZOZJlQZQhdj4D8kgIhjGfYGcj9i'
Above, we use the master key
2tPOaZOZJlQZQhdj4D8kgIhjGfYGcj9i. You must generate this master key yourself. You can use the
openssl tool for this:
$ openssl rand -base64 24
If you view the debug console for your dashboard when you trigger an event, you should see an encrypted payload:
Now for the other side: decrypting. Happily, there is nothing to do here: pusher-js automatically decrypts the event body on
How does end-to-end encryption work?
For each encrypted channel, there is a shared secret between your server and the subscribers to that channel. This channel shared secret is used by the server to encrypt a message to that channel, and by each subscriber to decrypt the message when received.
The channel shared secret is provided to subscribers in the response to an auth endpoint request. This requires no changes to your auth endpoint code:
// This will include the channel shared secret automatically
echo $pusher->socket_auth('private-encrypted-dr-42', $socket_id);
The channel shared secret is derived by the SDK from the channel name and your master key. This means that your server does not have to keep track of a large number of shared secrets. For more details, this series of slides shows the rationale behind the design:
What does end-to-end encryption not do?
Our implementation of end-to-end encryption provides the key property that Pusher cannot read or forge your messages. However, there are some properties that this does not provide:
- Forward secrecy. A leaked secret can be used to decrypt past messages. Forward secrecy would be difficult to implement because known methods require ordered and guaranteed message delivery.
- Prevention against replay attacks. In theory, Pusher could deliver your message multiple times, with malicious intent. (Of course, we don’t do this!)
- Encrypted client events. Client events are still readable by Pusher. (We are considering encrypted client events in future.)
- Encrypted channel names and encrypted event names. The channel name and event name are still visible to Pusher. (We could in future encrypt both of these.)
Where to go from here?
End-to-end encryption is in beta! It’s currently implemented in most SDKs. See our documentation to try it out! If you would like to see support in more SDKs, or for any other suggestions or advice, please contact us at [email protected].