Skip to main content

Media Stream Encryption

Introduction​

To improve data security, Agora supports encrypting users' media streams during real-time engagement. You can choose from the following encryption options according to your needs:

  • Built-in encryption: Use the preset encryption mode in the SDK to encrypt the media streams.
  • Customized encryption: Use the packet observer provided by the SDK to customize the encryption mode of media streams.

The following diagram describes the encrypted data transmission process:

1624372878076

Sample project​

Agora provides an open-source sample project that implements built-in encryption on GitHub. You can try the demo and view the source code.

Implementation​

Before enabling media-stream encryption, ensure that you refer to the appropriate Quickstart Guide to implement the basic real-time communication functions in your project.

Use the built-in encryption​

Before joining a channel, call enableEncryption to enable the built-in encryption.

As of v3.4.5, Agora recommends using the AES_128_GCM2 or AES_256_GCM2 encryption mode and setting the key and salt.

To generate and set the key and salt parameters, refer to the following steps.

  • All users in a channel must use the same encryption mode, key, and salt.
  • Compared to other encryption modes, the GCM2 encryption modes use a more secure KDF (Key Derivation Function) and support setting the salt. If you choose other encryption modes, you only need to set the encryption mode and key.
  • Generate and set the key​

    1. Refer to the following command to randomly generate a 32-byte key in the string format through OpenSSL on your server.

    _3
    // Randomly generate a 32-byte key in the string format, and pass the string key in the encryptionKey parameter of enableEncryption.
    _3
    openssl rand -hex 32
    _3
    dba643c8ba6b6dc738df43d9fd624293b4b12d87a60f518253bd10ba98c48453

    1. The client gets the key in the string format from the server and passes it to the SDK in the enableEncryption method.

    Generate and set the salt​

    1. Refer to the following command to randomly generate a Base64-encoded, 32-byte salt through OpenSSL on the server. You can also refer to the C++ sample code provided by Agora on GitHub to randomly generate a salt in the byte array format and convert it to Base64 on the server.

    _3
    // Randomly generate a 32-byte salt in the Base64 format. Convert the salt from Base64 to uint8_t, and pass the uint8_t salt in the encryptionKdfSalt parameter of enableEncryption.
    _3
    openssl rand -base64 32
    _3
    X5w9T+50kzxVOnkJKiY/lUk82/bES2kATOt3vBuGEDw=

    1. The client gets the Base64 salt from the server.

    2. The client decodes the salt value from Base64 encoding to a byte[] of length 32, and then passes it to the SDK in the enableEncryption method.

    Sample code​


    _23
    import java.util.Base64;
    _23
    import io.agora.rtc.RtcEngine;
    _23
    _23
    class Example
    _23
    {
    _23
    public bool enableEncryption(RtcEngine engine) {
    _23
    if(engine == null)
    _23
    return false;
    _23
    _23
    String encryptionKdfSaltBase64 = Server.getEncryptionKdfSaltBase64();
    _23
    String encryptionSecret = Server.getEncryptionSecret();
    _23
    if(encryptionKdfSaltBase64 == null || encryptionSecret == null)
    _23
    return false;
    _23
    _23
    byte[] encryptionKdfSalt = Base64.getDecoder().decode(encryptionKdfSaltBase64);
    _23
    EncryptionConfig config = new EncryptionConfig();
    _23
    config.encryptionMode = EncryptionConfig.EncryptionMode.AES_128_GCM2;
    _23
    config.encryptionKey = encryptionSecret;
    _23
    System.arraycopy(encryptionKdfSalt, 0, config.encryptionKdfSalt, 0, config.encryptionKdfSalt.length);
    _23
    int result = engine.enableEncryption(true, config);
    _23
    return (result == 0);
    _23
    }
    _23
    }

    API reference​

    enableEncryption

    Use the customized encryption​

    To implement the customized encryption, use IPacketObserver class and registerPacketObserver in C++ as follows:

    1. Before joining a channel, call registerPacketObserver to register the packet observer, so that you can receive events during audio or video packet transmission.


      _1
      virtual int registerPacketObserver(IPacketObserver* observer);

    2. Implement an IPacketObserver class.


      _44
      class IPacketObserver
      _44
      {
      _44
      public:
      _44
      _44
      struct Packet
      _44
      {
      _44
      // Buffer address of the sent or received data.
      _44
      const unsigned char* buffer;
      _44
      // Buffer size of the sent or received data.
      _44
      unsigned int size;
      _44
      };
      _44
      _44
      // Occurs when the local user sends an audio packet.
      _44
      // The SDK triggers this callback before the audio packet is sent to the remote user.
      _44
      // @param packet See Packet.
      _44
      // @return
      _44
      // - true: The audio packet is sent successfully.
      _44
      // - false: The audio packet is discarded.
      _44
      virtual bool onSendAudioPacket(Packet& packet) = 0;
      _44
      _44
      // Occurs when the local user sends a video packet.
      _44
      // The SDK triggers this callback before the video packet is sent to the remote user.
      _44
      // @param packet See Packet.
      _44
      // @return
      _44
      // - true: The video packet is sent successfully.
      _44
      // - false: The video packet is discarded.
      _44
      virtual bool onSendVideoPacket(Packet& packet) = 0;
      _44
      _44
      // Occurs when the local user receives an audio packet.
      _44
      // The SDK triggers this callback before the audio packet of the remote user is received.
      _44
      // @param packet See Packet.
      _44
      // @return
      _44
      // - true: The audio packet is sent successfully.
      _44
      // - false: The audio packet is discarded.
      _44
      virtual bool onReceiveAudioPacket(Packet& packet) = 0;
      _44
      _44
      // Occurs when the local user receives a video packet.
      _44
      // The SDK triggers this callback before the video packet of the remote user is received.
      _44
      // @param packet See Packet.
      _44
      // @return
      _44
      // - true: The video packet is sent successfully.
      _44
      // - false: The video packet is discarded.
      _44
      virtual bool onReceiveVideoPacket(Packet& packet) = 0;
      _44
      };

    3. Inherit the IPacketObserver class and use your customized encryption algorithm on your app.


      _90
      class AgoraRTCPacketObserver : public agora::rtc::IPacketObserver
      _90
      {
      _90
      public:
      _90
      EVP_CIPHER_CTX *ctx_audio_send;
      _90
      EVP_CIPHER_CTX *ctx_audio_receive;
      _90
      EVP_CIPHER_CTX *ctx_video_send;
      _90
      EVP_CIPHER_CTX *ctx_video_receive;
      _90
      AgoraRTCPacketObserver()
      _90
      {
      _90
      __android_log_print(ANDROID_LOG_INFO, "agoraencryption", "AgoraRTCPacketObserver0");
      _90
      m_txAudioBuffer.resize(1024);
      _90
      m_rxAudioBuffer.resize(1024);
      _90
      m_txVideoBuffer.resize(1024);
      _90
      m_rxVideoBuffer.resize(1024);
      _90
      __android_log_print(ANDROID_LOG_INFO, "agoraencryption", "AgoraRTCPacketObserver1");
      _90
      }
      _90
      _90
      virtual bool onSendAudioPacket(Packet& packet)
      _90
      {
      _90
      _90
      int outlen;
      _90
      // Encrypts the packet.
      _90
      unsigned char outbuf[1024];
      _90
      EVP_EncryptInit_ex(ctx_audio_send, EVP_aes_256_gcm(), NULL, NULL, NULL);
      _90
      EVP_CIPHER_CTX_ctrl(ctx_audio_send, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      _90
      EVP_EncryptInit_ex(ctx_audio_send, NULL, NULL, gcm_key, gcm_iv);
      _90
      EVP_EncryptUpdate(ctx_audio_send, outbuf, &outlen, packet.buffer, packet.size);
      _90
      // Sends the buffer and size of the encrypted data to the SDK.
      _90
      packet.buffer = outbuf;
      _90
      packet.size = outlen;
      _90
      return true;
      _90
      }
      _90
      _90
      virtual bool onSendVideoPacket(Packet& packet)
      _90
      {
      _90
      _90
      int outlen;
      _90
      // Encrypts the packet.
      _90
      unsigned char outbuf[1024];
      _90
      EVP_EncryptInit_ex(ctx_video_send, EVP_aes_256_gcm(), NULL, NULL, NULL);
      _90
      EVP_CIPHER_CTX_ctrl(ctx_video_send, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      _90
      EVP_EncryptInit_ex(ctx_video_send, NULL, NULL, gcm_key, gcm_iv);
      _90
      EVP_EncryptUpdate(ctx_video_send, outbuf, &outlen, packet.buffer, packet.size);
      _90
      // Sends the buffer and size of the encrypted data to the SDK.
      _90
      packet.buffer = outbuf;
      _90
      packet.size = outlen;
      _90
      return true;
      _90
      }
      _90
      _90
      virtual bool onReceiveAudioPacket(Packet& packet)
      _90
      {
      _90
      int outlen;
      _90
      // Decrypts the packet.
      _90
      unsigned char outbuf[1024];
      _90
      EVP_DecryptInit_ex(ctx_audio_receive, EVP_aes_256_gcm(), NULL, NULL, NULL);
      _90
      EVP_CIPHER_CTX_ctrl(ctx_audio_receive, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      _90
      EVP_DecryptInit_ex(ctx_audio_receive, NULL, NULL, gcm_key, gcm_iv);
      _90
      EVP_DecryptUpdate(ctx_audio_receive, outbuf, &outlen, packet.buffer, packet.size);
      _90
      // Sends the buffer and size of the decrypted data to the SDK.
      _90
      packet.buffer = outbuf;
      _90
      packet.size = outlen;
      _90
      return true;
      _90
      }
      _90
      _90
      virtual bool onReceiveVideoPacket(Packet& packet)
      _90
      {
      _90
      int outlen;
      _90
      // Decrypts the packet.
      _90
      unsigned char outbuf[1024];
      _90
      EVP_DecryptInit_ex(ctx_video_receive, EVP_aes_256_gcm(), NULL, NULL, NULL);
      _90
      EVP_CIPHER_CTX_ctrl(ctx_video_receive, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      _90
      EVP_DecryptInit_ex(ctx_video_receive, NULL, NULL, gcm_key, gcm_iv);
      _90
      EVP_DecryptUpdate(ctx_video_receive, outbuf, &outlen, packet.buffer, packet.size);
      _90
      // Sends the buffer and size of the decrypted data to the SDK.
      _90
      packet.buffer = outbuf;
      _90
      packet.size = outlen;
      _90
      return true;
      _90
      }
      _90
      _90
      private:
      _90
      // Sends the audio buffer.
      _90
      std::vector<unsigned char> m_txAudioBuffer;
      _90
      // Sends the video buffer.
      _90
      std::vector<unsigned char> m_txVideoBuffer;
      _90
      _90
      // Receives the audio buffer.
      _90
      std::vector<unsigned char> m_rxAudioBuffer;
      _90
      // Receives the video buffer.
      _90
      std::vector<unsigned char> m_rxVideoBuffer;
      _90
      };

    4. Implement a Java wrapper. You can refer to the following example:


      _15
      JNIEXPORT void JNICALL
      _15
      Java_io_agora_api_streamencrypt_PacketProcessor_doRegisterProcessing
      _15
      (JNIEnv *env, jobject obj)
      _15
      {
      _15
      __android_log_print(ANDROID_LOG_INFO, "agoraencryption", "doRegisterProcessing0");
      _15
      if (!rtcEngine) return;
      _15
      __android_log_print(ANDROID_LOG_INFO, "agoraencryption", "doRegisterProcessing1");
      _15
      // Registers the packet observer.
      _15
      int code = rtcEngine->registerPacketObserver(&s_packetObserver);
      _15
      __android_log_print(ANDROID_LOG_INFO, "agoraencryption", "%d", code);
      _15
      s_packetObserver.ctx_audio_send = EVP_CIPHER_CTX_new();
      _15
      s_packetObserver.ctx_video_send = EVP_CIPHER_CTX_new();
      _15
      s_packetObserver.ctx_audio_receive = EVP_CIPHER_CTX_new();
      _15
      s_packetObserver.ctx_video_receive = EVP_CIPHER_CTX_new();
      _15
      }

    5. Call registerAgoraPacketObserver implemented in step 4 to register the IPacketObserver instance.

    To unregister the packet observer, call the registerPacketObserver(nullptr) method after leaving the channel.

    API reference​

    registerPacketObserver

    Considerations​

    • Both the communication and interactive live streaming scenarios support encryption, but Agora does not support pushing encrypted streams to the CDN during live streaming.
    • To use media-stream encryption, you need to enable encryption before joining a channel. Ensure that both the receivers and senders use the same encryption mode; otherwise, undefined behaviors such as a black screen or audio loss occur.
    • To enhance security, Agora recommends using a new key and salt every time you enable media-stream encryption.