Skip to main content

You are looking at Voice Calling v3.x Docs. The newest version is  Voice Calling 4.x

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.

    Voice Calling