Skip to main content
Version: 3.x.x

End-to-End Encryption (E2EE) - Android

Overview​

End-to-end encryption (E2EE) ensures that your media content remains private and secure by encrypting it on the sender's device and decrypting it only on the receiver's device. This prevents intermediaries, including VideoSDK servers, from accessing or modifying the content.

E2EE is particularly important for applications in security-critical domains such as telehealth, finance, and legal services.

E2EE Modes​

VideoSDK offers two modes of End-to-End Encryption (E2EE) for securing media streams during sessions:

  1. Shared Key (Room-Level): A single encryption key is used across the entire session, encrypting and decrypting media tracks for all participants.
  2. Per-Participant Keys: Individual participants have unique encryption keys, so each participant's media streams are encrypted separately.

How E2EE Works​

  • E2EE is applied at the session level, where in Shared Key mode, a single key encrypts and decrypts all media tracks for every participant, while in Per-Participant Keys mode, each participant is assigned a unique key to encrypt their individual media streams separately.
  • Participants without the key cannot access the media.
  • The entire encryption process is handled client-side. VideoSDK does not access or store any encryption keys.

Key Management & Distribution​

You are fully responsible for generating, managing, and securely distributing encryption keys to all participants. VideoSDK never stores, accesses, or transfers your encryption keys.

  • Common Key Distribution Approaches:
    • Generate the key on your server when creating a room.
    • Send the key securely along with the room access token.
    • Use HTTPS-secured API calls to fetch the key on the client.
important

⚠️ Ensure that all participants have access to the correct encryption key before joining the room.

SDK Version Compatibility​

The table below lists the minimum SDK version that supports E2EE.

Android
0.5.0

In Android SDK 3.x, per-participant keys are set with VideoSDK.setParticipantKey(), as shown below.

Enabling End-to-End Encryption (E2EE) in VideoSDK Android​

VideoSDK provides two ways to enable End-to-End Encryption (E2EE):

  1. Shared Key: A single encryption key shared by all participants.
  2. Per-Participant Key: Unique encryption keys assigned to individual participants.

1. Enabling E2EE using a Shared Key​

When using a shared key, all participants utilize the same encryption key, set via the setSharedKey() method.

class MainActivity : AppCompatActivity() {

private var room: Room? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Initialize the encryption key using Shared Key approach
keySetup("SHARED_KEY") // Replace with your actual shared key

// Create the room
room = VideoSDK.createRoom(roomId = "ROOM_ID", token = "YOUR_TOKEN")
}

private fun keySetup(sharedKey: String) {
val baseKeyProvider = BaseKeyProvider()

// Set a single shared encryption key for all participants
baseKeyProvider.setSharedKey(sharedKey)

// Set the key provider for E2EE
VideoSDK.setKeyProvider(baseKeyProvider)
}
}
warning

Set the encryption key with setSharedKey() before you pass the provider to setKeyProvider(), which reads the keys at that moment. setSharedKey() throws an IllegalArgumentException for an empty key.

Call setKeyProvider() before join(). A key provider set after joining does not encrypt the room that is already running.

2. Enabling E2EE using Per-Participant Keys​

Per-Participant Keys assign a distinct encryption key to each participant.

Step 1. Set the Key Provider and the Local Participant's Key​
  • Turn on E2EE with VideoSDK.setKeyProvider(), passing a BaseKeyProvider with no shared key.
  • Then set the localParticipantId along with its corresponding key using VideoSDK.setParticipantKey().
  • Pass the same localParticipantId as participantId to VideoSDK.createRoom().
  • Don't set a shared key on the BaseKeyProvider. While a shared key is set, per-participant keys are ignored.
  • Save each participant’s ID and encryption key securely to your server for later use in decryption.
important

Call setKeyProvider() once, before any setParticipantKey() call. setParticipantKey() throws 3073 (ERROR_INVALID_PARAMETER) if no key provider was set. Keep the same provider for both encryption (local participant) and decryption (remote participants).

class MainActivity : AppCompatActivity() {

private var room: Room? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val localParticipantId = "YOUR_LOCAL_PARTICIPANT_ID"

// Set up the key provider and the local participant's key
keyProviderSetup(localParticipantId, "SECRET_KEY")

// Create the room
room = VideoSDK.createRoom(
roomId = "ROOM_ID",
token = "YOUR_TOKEN",
participantName = "PARTICIPANT_NAME",
micEnabled = true,
camEnabled = true,
participantId = localParticipantId,
multiStream = false,
)
}

// Setup Method
private fun keyProviderSetup(localParticipantId: String, secretKey: String) {
// Turn on E2EE with a provider that has no shared key
VideoSDK.setKeyProvider(BaseKeyProvider())
// Per-Participant E2EE: Set key for local participant
VideoSDK.setParticipantKey(localParticipantId, secretKey) // Your Encryption Key
}
}
Step 2. Decrypting Participant Streams using Per-Participant Keys​
  • When a participant joins, retrieve the corresponding encryption key (previously stored on your server) using the participant’s ID to decrypt their media streams.
  • Set that key with VideoSDK.setParticipantKey().
  • setParticipantKey() also rotates a participant's key during the call. Passing null as the key drops that participant's key: this device then stops decrypting that participant until a new key is set.
  • keyProvider.removeKey(participantId) on the provider you passed to setKeyProvider() does the same as setParticipantKey(participantId, null).
private val roomEventListener = object : RoomEventListener() {

override fun onParticipantJoined(participant: Participant) {

// Retrieve the encryption key for the participant from your server
val secretKey = "SECRET_KEY"

// Set the key to decrypt incoming participant streams
VideoSDK.setParticipantKey(participant.id, secretKey)
}
}
important
  • Ensure all encryption keys are securely stored and retrieved from your server.
  • Proper key management is essential for effective encryption and decryption, especially when using Per-Participant Keys.
  • Set a key for every participant. Without one, this device does not decrypt that participant's media.
  • Per-participant keys, your own included, are cleared when you leave the room. The shared key is kept. Set the per-participant keys again for every room you join.

Event for E2EE State Changes​

To monitor encryption/decryption state changes for each participant's media stream:

val e2eeListener = object : ParticipantEventListener() {
override fun onE2EEStateChanged(state: E2EEState, stream: RTCStream) {
when (state) {
E2EEState.EncryptionSuccess ->
Log.d("e2ee", "Encrypting my ${stream.kind}")
E2EEState.DecryptionSuccess ->
Log.d("e2ee", "Decrypting ${stream.kind} from ${stream.participantId}")
else -> Log.w("e2ee", "${stream.kind} -> $state")
}
}
}

// Encryption states of the media you send
room.localParticipant.addEventListener(e2eeListener)

// Decryption states of the media each remote participant sends.
// Also add it in onParticipantJoined() for participants who join later.
room.participants.values.forEach { it.addEventListener(e2eeListener) }
  • It reports EncryptionSuccess or DecryptionSuccess when encryption starts working on a stream.
  • Decryption failures are not reported. A wrong or removed key shows only as the stream going dark.
  • Encryption states are reported on the local participant, and decryption states on the remote participant who sent the media.
  • The event is reported only while a key provider is set with VideoSDK.setKeyProvider().
  • Use stream.kind to tell which of the participant's streams the state is about.

The possible values for state are:

StateDescription
EncryptionSuccessA stream this device sends is being encrypted. Reported once per stream, not per frame.
DecryptionSuccessA stream from a remote participant is being decrypted. Reported once per stream, not per frame.
EncryptionFailedMedia could not be encrypted, so it was not sent.
DecryptionFailedMedia from a remote participant could not be decrypted. Not reported.
InternalErrorEncryption failed inside the SDK.

To check whether E2EE is enabled​

room.e2eeEnabled is true when media is actually being encrypted, not merely when a key provider is set.

val isEnabled = room.e2eeEnabled

To join a room without E2EE after you have set a key provider, call VideoSDK.removeKeyProvider() before join().

Limitations​

E2EE only applies to media. It does not apply to:

  • Chat messages or metadata
  • API calls and signaling data

These communications are still protected by TLS but are not encrypted end-to-end.

info

Recording and Transcription features are not supported when End-to-End Encryption (E2EE) is enabled.

Got a Question? Ask us on discord