Skip to main content
The BaseKeyProvider class manages encryption keys for LiveKit’s end-to-end encryption. It supports both shared key mode (single key for all participants) and per-participant key mode.

Initialization

Shared Key Mode

Use a single shared key for all participants:
let keyProvider = BaseKeyProvider(isSharedKey: true, sharedKey: "my-secret-key")
Or set the key later:
let keyProvider = BaseKeyProvider(isSharedKey: true)
keyProvider.setKey(key: "my-secret-key")

Per-Participant Key Mode

Use different keys for each participant:
let keyProvider = BaseKeyProvider(isSharedKey: false)

// Set keys for specific participants
keyProvider.setKey(key: "alice-key", participantId: "alice")
keyProvider.setKey(key: "bob-key", participantId: "bob")

Custom Options

Create with custom KeyProviderOptions:
let options = KeyProviderOptions(
    sharedKey: true,
    ratchetSalt: customSalt,
    ratchetWindowSize: 16,
    uncryptedMagicBytes: customMagicBytes,
    failureTolerance: 5,
    keyRingSize: 32
)

let keyProvider = BaseKeyProvider(options: options)

Properties

options
KeyProviderOptions
Configuration options for the key provider.
rtcKeyProvider
LKRTCFrameCryptorKeyProvider
Internal WebRTC key provider (for advanced use).

KeyProviderOptions

sharedKey
Bool
default:"true"
Whether to use a single shared key for all participants.
ratchetSalt
Data
default:"LKFrameEncryptionKey"
Salt used for key ratcheting.
ratchetWindowSize
Int32
default:"0"
Window size for key ratcheting.Default 0 disables automatic ratcheting for shared key mode.
uncryptedMagicBytes
Data
default:"LK-ROCKS"
Magic bytes added to unencrypted frames for identification.
failureTolerance
Int32
default:"-1"
Number of consecutive decryption failures to tolerate.-1 means unlimited tolerance.
keyRingSize
Int32
default:"16"
Size of the key ring buffer.

Methods

setKey(key:participantId:index:)

Set an encryption key.
// Shared key mode
keyProvider.setKey(key: "my-secret-key")

// Per-participant mode
keyProvider.setKey(key: "participant-key", participantId: "participant-id")

// With specific index
keyProvider.setKey(key: "my-key", index: 5)
Parameters:
  • key: The encryption key as a string
  • participantId: Participant identifier (required for per-participant mode)
  • index: Key index in the key ring (optional, defaults to current key index)

ratchetKey(participantId:index:)

Ratchet (rotate) a key using a one-way function.
// Shared key mode
if let newKey = keyProvider.ratchetKey() {
    print("Key ratcheted: \(newKey.base64EncodedString())")
}

// Per-participant mode
if let newKey = keyProvider.ratchetKey(participantId: "participant-id") {
    print("Participant key ratcheted")
}
Returns: The new ratcheted key as Data?, or nil on failure. Parameters:
  • participantId: Participant identifier (required for per-participant mode)
  • index: Key index to ratchet (optional)

exportKey(participantId:index:)

Export a key from the key provider.
// Shared key mode
if let keyData = keyProvider.exportKey() {
    let keyString = String(data: keyData, encoding: .utf8)
    print("Exported key: \(keyString ?? "<binary>")")
}

// Per-participant mode  
if let keyData = keyProvider.exportKey(participantId: "participant-id") {
    // Use the exported key
}
Returns: The key as Data?, or nil if the key doesn’t exist. Parameters:
  • participantId: Participant identifier (required for per-participant mode)
  • index: Key index to export (optional)

getCurrentKeyIndex()

Get the current key index.
let index = keyProvider.getCurrentKeyIndex()
print("Current key index: \(index)")
Returns: The current key index as Int32.

setCurrentKeyIndex(_:)

Set the current key index.
keyProvider.setCurrentKeyIndex(5)
The index is automatically wrapped using modulo with keyRingSize.

setSifTrailer(_:)

Set the SIF (Selective Forwarding) trailer.
let trailer = Data([0x01, 0x02, 0x03])
keyProvider.setSifTrailer(trailer: trailer)

Usage Examples

Basic Shared Key Setup

import LiveKit

// Create key provider with shared key
let keyProvider = BaseKeyProvider(isSharedKey: true)

// Set the encryption key
keyProvider.setKey(key: "my-secret-encryption-key")

// Use with E2EE
let encryptionOptions = EncryptionOptions(
    keyProvider: keyProvider,
    encryptionType: .gcm
)

let roomOptions = RoomOptions(encryptionOptions: encryptionOptions)
let room = Room(roomOptions: roomOptions)

Key Rotation

class KeyRotationManager {
    let keyProvider: BaseKeyProvider
    
    init() {
        keyProvider = BaseKeyProvider(isSharedKey: true)
        keyProvider.setKey(key: "initial-key")
    }
    
    func rotateKey() {
        // Generate new key (example using UUID)
        let newKey = UUID().uuidString
        
        // Update to next key index
        let currentIndex = keyProvider.getCurrentKeyIndex()
        keyProvider.setKey(key: newKey, index: currentIndex + 1)
        keyProvider.setCurrentKeyIndex(currentIndex + 1)
        
        print("Rotated to key index \(keyProvider.getCurrentKeyIndex())")
    }
    
    func scheduleRotation() {
        // Rotate key every hour
        Timer.scheduledTimer(withTimeInterval: 3600, repeats: true) { _ in
            self.rotateKey()
        }
    }
}

Per-Participant Keys

class PerParticipantEncryption: RoomDelegate {
    let keyProvider: BaseKeyProvider
    let room: Room
    
    init() {
        // Use per-participant key mode
        keyProvider = BaseKeyProvider(isSharedKey: false)
        
        let encryptionOptions = EncryptionOptions(
            keyProvider: keyProvider,
            encryptionType: .gcm
        )
        
        let roomOptions = RoomOptions(encryptionOptions: encryptionOptions)
        room = Room(roomOptions: roomOptions)
        room.add(delegate: self)
    }
    
    // Set key when participant joins
    func room(_ room: Room, participantDidConnect participant: RemoteParticipant) {
        guard let participantId = participant.identity?.stringValue else { return }
        
        // Fetch key for this participant (from your backend)
        fetchKeyForParticipant(participantId) { key in
            self.keyProvider.setKey(key: key, participantId: participantId)
        }
    }
    
    func fetchKeyForParticipant(_ id: String, completion: @escaping (String) -> Void) {
        // Implementation to fetch key from your server
        // ...
    }
}

Key Ratcheting

class RatchetingKeyProvider {
    let keyProvider: BaseKeyProvider
    
    init() {
        let options = KeyProviderOptions(
            sharedKey: true,
            ratchetWindowSize: 16  // Enable ratcheting with window size
        )
        keyProvider = BaseKeyProvider(options: options)
    }
    
    func setupInitialKey() {
        keyProvider.setKey(key: "initial-secret-key")
    }
    
    func performRatchet() {
        if let newKey = keyProvider.ratchetKey() {
            print("Key ratcheted successfully")
            print("New key (base64): \(newKey.base64EncodedString())")
        } else {
            print("Failed to ratchet key")
        }
    }
    
    func exportCurrentKey() -> String? {
        guard let keyData = keyProvider.exportKey() else {
            return nil
        }
        return keyData.base64EncodedString()
    }
}

Best Practices

  1. Choose the Right Mode
    • Use shared key mode for simplicity and when all participants should have the same access
    • Use per-participant mode for fine-grained control and better security
  2. Secure Key Storage
    • Never hardcode keys in your app
    • Fetch keys from your secure backend
    • Use iOS Keychain for local key storage
  3. Key Rotation
    • Implement regular key rotation for better security
    • Coordinate rotation across all participants
    • Keep old keys in the ring temporarily for transition
  4. Key Distribution
    • Use your own secure channel to distribute keys
    • Never send keys through the LiveKit data channel unencrypted
    • Consider using key exchange protocols (e.g., Diffie-Hellman)
  5. Error Handling
    • Monitor E2EE state through RoomDelegate
    • Handle missingKey state by requesting keys from backend
    • Implement retry logic for key distribution failures

Build docs developers (and LLMs) love