Skip to main content
Version: 2.x.x

End-to-End Encryption (E2EE) - Flutter

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 (Meeting-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, providing enhanced security by ensuring 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 meeting.
    • Send the key securely along with the meeting 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 versions that support E2EE.

FlutteriOSReact NativeReactAndroidJavaScript
2.1.02.2.20.2.10.3.5Coming Soon0.2.6

Enabling E2EE in VideoSDK Flutter

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.

@override  
void initState() {
super.initState();

// Set up the encryption key
keySetup();

// Initialize the meeting instance
Room room = VideoSDK.createRoom(
roomId: meetingId,
token: token,
displayName: displayName,
);
}

Future<void> keySetup() async{
try {
var baseKeyProvider = BaseKeyProvider();

// Set a single shared encryption key for all participants
await baseKeyProvider.setSharedKey('<Your Encryption Key>');

// Set the key provider for E2EE
VideoSDK.setKeyProvider(baseKeyProvider);
} catch (e) {
print("Error setting encryption key: ${e.toString()}");
}
}
warning

Make sure to set the encryption key using setSharedKey() before assigning the key provider with setKeyProvider(), otherwise it will throw an Exception.

2. Enabling E2EE using Per-Participant Keys

Per-Participant Keys enhance security by assigning a distinct encryption key to each participant, ensuring that access is uniquely controlled on a per-user basis.

Step 1. Initialize the BaseKeyProvider and Set Up Encryption Keys
  • To enable encryption, you must set the localParticipantId along with its corresponding key using the setKey() method of the BaseKeyProvider class.
  • Store each participant's ID and key securely on your server for decryption purposes.
important

You must use a single, shared BaseKeyProvider instance for both encryption (local participant) and decryption (remote participants).

class MeetingScreen extends StatefulWidget {
const MeetingScreen({super.key});

@override
State<MeetingScreen> createState() => _MeetingScreenState();
}

class _MeetingScreenState extends State<MeetingScreen> {

// Declare globally
BaseKeyProvider baseKeyProvider = BaseKeyProvider();

@override
void initState() {
super.initState();

String localParticipantId = "YOUR_LOCAL_PARTICIPANT_ID";

// Set up the encryption key
keySetup(localParticipantId , "SECRET_KEY");

// Initialize the meeting instance
Room room = VideoSDK.createRoom(
roomId: meetingId,
token: token,
displayName: displayName,
participantId: localParticipantId,
);
}

Future<void> keySetup(String participantId , String key) async {
try {
// Per-Participant E2EE: Set key for local participant
await baseKeyProvider.setKey(participantId, key); // Your Encryption Key
VideoSDK.setKeyProvider(baseKeyProvider);
} catch (e) {
log("Error in setting the keys: ${e.toString()}");
}
}
}
warning

Make sure to set the encryption key using setKey() before assigning the key provider with setKeyProvider(), otherwise it will throw an Exception.

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.
  • Use the same BaseKeyProvider instance to set the key for that remote participant to decrypt their media stream.
class MeetingScreen extends StatefulWidget {
const MeetingScreen({super.key});

@override
State<MeetingScreen> createState() => _MeetingScreenState();
}

class _MeetingScreenState extends State<MeetingScreen> {

@override
void initState() {
super.initState();

// Create instance of Room (Meeting)
Room room = VideoSDK.createRoom(
roomId: meetingId,
token: token,
displayName: displayName,
participantId: participantId,
);

// Regiser Meeting event
registerMeetingEvents(room);
}

void registerMeetingEvents(Room room) {

room.on(Events.participantJoined, (Participant participant) {
try {
// Retrieve key from your server using participant.id
String key = "SECRET_KEY";

// Set the retrieved key to decrypt incoming participant streams
baseKeyProvider.setKey(participant.id,key);
} catch (e) {
log("Error in setting the keys: ${e.toString()}");
}
});
}
}
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.

Additional Configuration Options

You can customize the encryption behavior by passing parameters while creating the BaseKeyProvider instance:

ParameterTypeDescription
discardFrameWhenCryptorNotReadyboolIf true, frames will be discarded when the cryptor is not ready, ensuring no unencrypted content is sent.

E2EE in Flutter Web

For Flutter Web, a dedicated worker file is required to handle cryptographic operations. Run the following command in your project root:

dart run videosdk:install_worker

This will install the required worker file inside your web directory.

Event for the e2ee state

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

widget.participant.on(Events.e2eeStateChanged, (
E2EEState state,
Stream stream,
) {
print("$state is for the ${stream.kind}");
});

The possible values for state are:

StateDescription
EncryptionSuccessMedia encryption is successfully applied.
DecryptionSuccessIncoming media is successfully decrypted.
EncryptionFailedEncryption encountered an error.
DecryptionFailedDecryption encountered an error.
InternalErrorInternal processing error occurred.

To check whether E2EE is enabled

You can check whether E2EE is enabled by using e2eeEnabled property of Room class.

bool isE2EEEnabled = _room.e2eeEnabled;
print("Is E2EE Enabled: $isE2EEEnabled");

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