End-to-end encryption (Beta)

Updated

Implement end-to-end encryption to ensure data security during transmission.

Supported platform: Web.

To enhance data security, Agora provides media stream encryption and end-to-end encryption. End-to-end encryption encrypts the media stream on the sending device and decrypts it on the receiving device, ensuring that third parties cannot access the data during transmission.

The end-to-end encryption is based on the WebRTC Encoded Transform standard. However, since WebRTC Encoded Transform is still maturing and may have compatibility or performance issues, there are some restrictions on its use:

  • Only VP8 video and Opus audio are supported.
  • It supports publishing and receiving audio and video streams between Web clients only, with no cross-platform compatibility or bypass streaming services.
  • The SEI information transmission feature cannot be enabled due to conflicts with browser components.

Understand the tech

The end-to-end encryption technology is based on the following key concepts:

  • WebRTC Encoded Transform: A technology for encoding transformations in WebRTC. It provides a read and write interface for encoded WebRTC data, enabling developers to convert encoding formats, adjust bit rates, or apply filters.
  • WebRTC Encoded Transform processing pipeline: A sequence of operations used to process WebRTC encoded data.
  • RTCRtpSender and RTCRtpReceiver: Standard WebRTC components. RTCRtpSender handles sending encoded data, while RTCRtpReceiver is responsible for receiving it. Together, they form the WebRTC Encoded Transform processing pipeline.
  • RTCRtpTransceiver: A WebRTC API for managing both audio and video streams. RTCRtpSender and RTCRtpReceiver are accessible through the .sender and .receiver properties of the RTCRtpTransceiver object.

For each audio or video track, the Web SDK offers the getRTCRtpTransceiver method to retrieve the RTCRtpTransceiver. After obtaining RTCRtpSender and RTCRtpReceiver from RTCRtpTransceiver, set up the WebRTC Encoded Transform processing pipeline to encrypt and decrypt audio or video data.

Prerequisites

  • Ensure that you have implemented the SDK quickstart in your project.
  • A supported browser:
    • Chrome (desktop and mobile): version 87 or later
    • Safari (desktop and mobile): version 15.4 or later
  • Contact support@agora.io to enable end-to-end encryption.

Implement end-to-end encryption

This section shows you how to implement video encryption on the sender side and decryption on the receiver side using Chrome as an example. The steps for Safari are slightly different.

The example uses the AES encryption algorithm from the Web Crypto API. To implement a custom encryption and decryption algorithm, use WebAssembly.

  1. Publish the encrypted media stream

    To establish a WebRTC Encoded Transform processing pipeline on the publishing end:

    1. After publishing the local video track, call the getRTCRtpTransceiver method provided by the SDK to obtain the local video track's RTCRtpTransceiver instance. Then, retrieve the RTCRtpSender instance through the sender property.

    2. Use the browser's native Streams API to encrypt the video stream.

    Refer to the following sample code:

    async function publish() {
      // Publish the local video track
      await client.publish([localVideoTrack]);
    
      // Get the RTCRtpTransceiver instance for the local video track
      const transceiver = localVideoTrack.getRTCRtpTransceiver();
      if (!transceiver || !transceiver.sender) {
        return;
      }
      // Get the RTCRtpSender to control the media data being sent
      const sender = transceiver.sender;
    
      // Define the encryption method for the Chrome browser
      if (isChrome) {
        // Create an encoded stream for encryption transformation
        const streams = sender.createEncodedStreams();
        // Create the encryption transformer
        const transformer = new TransformStream({
          transform(chunk, controller) {
            // The first 7 bytes of VP8 video key frames and the first 3 bytes of non-key frames need to be preserved and not encrypted; otherwise, media transmission will fail
            const vp8ReservedSize = chunk.type === 'key' ? 7 : 3;
            // Implement the encrypt method yourself according to your encryption requirements
            // The example uses the AES encryption algorithm provided by the Web Crypto API
            const encryptedChunk = encrypt(chunk, key, vp8ReservedSize);
            controller.enqueue(encryptedChunk);
          },
        });
    
        // Connect the encoded stream and the encryption transformer and write the result to the data stream
        streams.readable.pipeThrough(transformer).pipeTo(streams.writable);
      }
    }
  2. Subscribe to the encrypted media stream

    To establish the WebRTC Encoded Transform processing pipeline on the receiving end:

    1. When subscribing to a remote video stream, call the getRTCRtpTransceiver method provided by the SDK to obtain an instance of the remote video track's RTCRtpTransceiver.

    2. Call the browser's native Streams API to decrypt the video stream.

    Refer to the following sample code:

    async function subscribe(user, mediaType) {
      // Get the RTCRtpTransceiver instance of the remote video track
      const transceiver = user.videoTrack.getRTCRtpTransceiver();
      if (!transceiver || !transceiver.receiver) {
        return;
      }
    
      // Get the RTCRtpReceiver to control the received media data
      const receiver = transceiver.receiver;
    
      // Define the decryption method for Chrome browser
      if (isChrome) {
        // Create an encoded stream for decryption
        const streams = receiver.createEncodedStreams();
        // Create a decryption transformer
        const transformer = new TransformStream({
          transform(chunk, controller) {
            // The first 7 bytes of VP8 video keyframes and the first 3 bytes of non-keyframes do not need decryption
            const vp8ReservedSize = chunk.type === 'key' ? 7 : 3;
            // Implement the decrypt method yourself according to your decryption requirements
            const decryptedChunk = decrypt(chunk, key, vp8ReservedSize);
            controller.enqueue(decryptedChunk);
          },
        });
    
        // Connect the encoded stream and the decryption transformer, and write the results to the data stream
        streams.readable.pipeThrough(transformer).pipeTo(streams.writable);
      }
    }

Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects of this product.

Development considerations

If the SDK disconnects and then reconnects, the RTCRtpTransceiver instance corresponding to the current track may change. Obtain the new RTCRtpTransceiver object through the following callbacks:

  • Local track: transceiver-updated
  • Remote track: transceiver-updated

After enabling end-to-end encryption, browser restrictions require creating an empty encryption pipeline for local tracks created using the Web SDK, even if encryption isn't needed. Without this, media data cannot be sent. Use the following code to create the empty encryption pipeline:

async function publish() {
  // Publish the local video track
  await client.publish([localVideoTrack]);

  // Get the RTCRtpTransceiver instance of the local video track
  const transceiver = localVideoTrack.getRTCRtpTransceiver();
  if (!transceiver || !transceiver.sender) {
    return;
  }
  // Get the RTCRtpSender to control the media being sent
  const sender = transceiver.sender;

  // Define the encryption mechanism for Chrome
  if (isChrome) {
    // Create encoded streams for transformation
    const streams = sender.createEncodedStreams();
    // Create a transform stream for handling the data
    const transformer = new TransformStream({
      transform(chunk, controller) {
        // Directly write back the data
        controller.enqueue(chunk);
      },
    });
    // Pipe the encoded stream through the transformer and write it to the data stream
    streams.readable.pipeThrough(transformer).pipeTo(streams.writable);
  }
}

API reference