Skip to main content
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)
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

e2eeOptions
E2EEOptions?
Legacy E2EE options if initialized with E2EEOptions.
options
EncryptionOptions?
Modern encryption options if initialized with EncryptionOptions.
keyProvider
BaseKeyProvider
The key provider managing encryption keys.Returns the key provider from options or e2eeOptions.
frameEncryptionType
EncryptionType
The encryption type used for media frames.Returns .none if no encryption is configured.
isDataChannelEncryptionEnabled
Bool
Whether data channel encryption is enabled.Only available when using EncryptionOptions.
dataChannelEncryptionType
EncryptionType
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.
e2eeManager.cleanUp()
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

  1. Key Rotation: Regularly rotate encryption keys for better security
    keyProvider.setKey(key: newKey)
    
  2. 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)
    
  3. Monitor Encryption State: Always implement room(_:trackPublication:didUpdateE2EEState:) to detect issues
  4. Clean Up: Call cleanUp() when disposing of the E2EE manager to release resources

Build docs developers (and LLMs) love