Encryption
End-to-End Message Encryption in River
All messages in the context of the communication modalities that River supports are encrypted. Nodes are only privy to ciphertext and clients manage their device encryption keys.
Two related cryptographic ratcheting algorithms are used by River to encrypt messages. River employs an implementation of the Double Ratchet cryptographic ratchet described by Signal to encrypt peer to peer messages. River also employs an AES-based cryptographic ratched optimized for group messaging.
Devices
To facilitate end to end encryption in River, each (user, client instance)
tuple is associated with a deviceId
, which identifies that device and is used to establish peer-to-peer encrypted sessions for the purpose of sharing group message encryption keys.
Devices are objects storing key material created on the client and stored in the River Node on the user’s UserDeviceKey
stream containing the following pair of keys:
- Curve25519 peer-to-peer encryption key - A long-lived Curve25519 asymmetric key pair is created with a new device of a user. The private portion of this key never leaves the device, while the public portion is stored in the user’s
UserDeviceKey
stream. This key along with the following key are used by other user’s to establish secure and ephemeral p2p sessions. - Curve25519 fallback key - A second Curve25519 key pair is created using the encryption key and published to a user’s UserDeviceKey stream. For Alice to establish a new secure p2p encrypted session with Bob, Alice would use her encryption key along with Bob’s public key portion of his encryption key and fallback key.
Device lifecycle is outside of the purview of the River protocol and managed entirely by client implementations. However, given it is expected under the protocol that there exists a 1-1 relation between
(user, client instance)
tuples anddevices
, the River Node performs periodic compaction criteria to stem the uncontrolled growth of user’s device key stream in storage.
Encryption Data Schemas
Encrypted data originating from messages or metadata, such as usernames, is described in the protocol with a protobuf message as follows.
The session_id
is used to identify the keys associated with the ciphertext, which can be used to decrypt the same message multiple times. This is particularly useful in a group messaging application as it avoids the need to re-establish peer-to-peer encrypted sessions
for each message.
Peer to peer encryption sessions are only used to transmit session keys corresponding to message events and are described by the following protobuf in the protocol.
A map is used to index ciphertext by the intended user’s deviceKey since peer-to-peer encrypted payloads are only able to be decrypted by the deviceKey that the outbound
sender’s session was created for. In general, peer-to-peer encrypted messages are encrypted in a per device basis.
Encryption Lifecycle
Below is an example of the encryption lifecycle between Alice and Bob who are co-members of a channel within a space.
- Alice logs in to her client, creates a new
device
, and joins a Space. - Bob who is already logged in and a member of the Space sees a
KeySolicitation
message with adevice_key
corresponding to Alice’s device. - Bob validates that Alice
isEntitled
to decryption keys for the channel stream that the solicitation event appeared in and creates a new p2p encryptedoutbound
session using Alice’s device key and fallback key to transmit the keys requested from his local cache. - Bob sends an
ack
to the stream to notify other co-members of the channel that Alice’s request is being worked on. - Alice sees Bob’s message on her
UserToDevice
key stream and created a newinbound
session with Bob’s device key and fallback key obtained from hisUserDeviceKey
stream. - Alice decrypts Bob’s message and extracts the key material storing it in her local cache.
- Alice attempts re-decrypting the channel messages that share the
session_id
of the keys she now has in her possession.
Peer-to-peer encryption in River requires distinct sessions
outbound
,inbound
for encryption and decryption, respectively. Moreover, each message can only be decrypted once per established session.
Key Sharing
Active Sharing
Session keys to encrypt message events and metadata in River are created on an as-needed basis by clients. If a user is joining a channel and decides to subsequently send a message for the first time, they will create a new outbound session key to encrypt the message and send it to the member roster of the channel along with the message. The same key can be used as an inbound session key by other members to decrypt the message encrypted with that session.
Passive Sharing
The above contrasts with passive key sharing, which is used to transmit keys that users are entitled to but do not have locally. When joining a channel for the first time, a user will sync stream events, but will need session keys to decrypt message events. The protocol supports an efficient key sharing scheme that has users place an KeySolicitation
event on the stream, which any online member of the stream will see and conditionally service if the solicitator is an entitled member of the stream.
Data
The protocol allows for any stream to support key sharing by way of KeySolicitation
, and KeyFulfillment
messages. Since fulfilling a solicitation requires creating a peer-to-peer encrypted session with the solicitator, the device_key
and fallback_key
are added to the payload to save a lookup against the UserDeviceKey
stream. Fulfillments are synced by members of the same stream to avoid the worst case behavior of every member fulfilling every request in a duplicative manner.
Was this page helpful?