The E2EEManager class manages end-to-end encryption for both media tracks (audio/video) and data channels in a LiveKit room.
Initialization
With E2EEOptions (Legacy)
let e2eeOptions = E2EEOptions(
keyProvider: myKeyProvider,
encryptionType: .gcm
)
let e2eeManager = E2EEManager(e2eeOptions: e2eeOptions)
With EncryptionOptions (Recommended)
let encryptionOptions = EncryptionOptions(
keyProvider: myKeyProvider,
encryptionType: .gcm
)
let e2eeManager = E2EEManager(options: encryptionOptions)
Use EncryptionOptions for new implementations. It supports both media track encryption and data channel encryption.
Properties
Legacy E2EE options if initialized with E2EEOptions.
Modern encryption options if initialized with EncryptionOptions.
The key provider managing encryption keys.Returns the key provider from options or e2eeOptions.
The encryption type used for media frames.Returns .none if no encryption is configured.
isDataChannelEncryptionEnabled
Whether data channel encryption is enabled.Only available when using EncryptionOptions.
dataChannelEncryptionType
The encryption type used for data channels.Returns .none if data channel encryption is not enabled.
Methods
setup(room:)
Set up the E2EE manager for a specific room.
let room = Room()
e2eeManager.setup(room: room)
This method:
- Registers the manager as a room delegate
- Sets up encryption for existing local and remote tracks
- Initializes data channel encryption if enabled
enableE2EE(enabled:)
Enable or disable end-to-end encryption.
// Enable encryption
e2eeManager.enableE2EE(enabled: true)
// Disable encryption
e2eeManager.enableE2EE(enabled: false)
This affects all frame cryptors that have been created.
cleanUp()
Clean up all encryption resources.
This method:
- Removes all frame cryptors
- Clears track publication references
- Cleans up data channel cryptor
Integration with Room
To use E2EE with a room, pass the encryption options when creating RoomOptions:
// Create key provider
let keyProvider = BaseKeyProvider(isSharedKey: true)
keyProvider.setKey(key: "my-secret-key")
// Create encryption options
let encryptionOptions = EncryptionOptions(
keyProvider: keyProvider,
encryptionType: .gcm
)
// Create room with encryption
let roomOptions = RoomOptions(
encryptionOptions: encryptionOptions
)
let room = Room(roomOptions: roomOptions)
The room will automatically create and manage the E2EEManager.
Encryption States
Monitor encryption state through the RoomDelegate:
func room(_ room: Room, trackPublication: TrackPublication, didUpdateE2EEState state: E2EEState) {
switch state {
case .new:
print("Encryption initialized")
case .ok:
print("Encryption working normally")
case .encryptionFailed:
print("Failed to encrypt")
case .decryptionFailed:
print("Failed to decrypt")
case .missingKey:
print("Encryption key is missing")
case .keyRatcheted:
print("Key was ratcheted")
case .internalError:
print("Internal encryption error")
}
}
Key Management
The E2EE manager works with a BaseKeyProvider to manage encryption keys:
// Access the key provider
let keyProvider = e2eeManager.keyProvider
// Update encryption key
keyProvider.setKey(key: "new-secret-key")
// For participant-specific keys (non-shared mode)
keyProvider.setKey(key: "participant-key", participantId: "participant-id")
Data Channel Encryption
When using EncryptionOptions, data sent through the room is automatically encrypted:
let encryptionOptions = EncryptionOptions(
keyProvider: keyProvider,
encryptionType: .gcm
)
let roomOptions = RoomOptions(encryptionOptions: encryptionOptions)
let room = Room(roomOptions: roomOptions)
// This data will be automatically encrypted
try await room.localParticipant.publish(data: myData, forTopic: "chat")
Received data is automatically decrypted and delivered through the delegate:
func room(_ room: Room, participant: RemoteParticipant?, didReceiveData data: Data, forTopic topic: String, encryptionType: EncryptionType) {
// data is already decrypted
print("Encryption type: \(encryptionType)")
}
Complete Example
import LiveKit
class EncryptedRoomManager: RoomDelegate {
var room: Room!
func setupEncryptedRoom() {
// Create and configure key provider
let keyProvider = BaseKeyProvider(isSharedKey: true)
keyProvider.setKey(key: "my-secret-encryption-key")
// Create encryption options
let encryptionOptions = EncryptionOptions(
keyProvider: keyProvider,
encryptionType: .gcm
)
// Create room with encryption
let roomOptions = RoomOptions(
encryptionOptions: encryptionOptions
)
room = Room(roomOptions: roomOptions)
room.add(delegate: self)
}
func connect() async throws {
try await room.connect(url: serverURL, token: token)
}
// Monitor encryption state
func room(_ room: Room, trackPublication: TrackPublication, didUpdateE2EEState state: E2EEState) {
print("Track \(trackPublication.sid): E2EE state = \(state)")
if state == .missingKey {
// Handle missing key - might need to request key from server
print("Encryption key is missing for track")
}
}
// Receive encrypted data
func room(_ room: Room, participant: RemoteParticipant?, didReceiveData data: Data, forTopic topic: String, encryptionType: EncryptionType) {
print("Received \(encryptionType) encrypted data on topic \(topic)")
// data is already decrypted and ready to use
}
}
Best Practices
-
Key Rotation: Regularly rotate encryption keys for better security
keyProvider.setKey(key: newKey)
-
Shared vs Per-Participant Keys: Use shared keys for simplicity, per-participant keys for granular control
// Shared key mode (simpler)
let keyProvider = BaseKeyProvider(isSharedKey: true)
// Per-participant mode (more secure)
let keyProvider = BaseKeyProvider(isSharedKey: false)
-
Monitor Encryption State: Always implement
room(_:trackPublication:didUpdateE2EEState:) to detect issues
-
Clean Up: Call
cleanUp() when disposing of the E2EE manager to release resources