Skip to main content

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

Android
iOS
macOS
Windows C++
Windows C#
Unity
Flutter
React Native
Electron
Cocos Creator
Cocos2d-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 the following open-source sample projects on GitHub.

You can download them and refer to 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 built-in encryption

1. Integrate the encryption library (iOS only)

Since v3.2.0, the AgoraRtcCryptoLoader.framework encryption library in the Agora iOS SDK has been merged into the AgoraRtcKit.framework library. If you use the SDK of v3.2.0 and latter version, you can use the built-in encryption scheme after integrating the AgoraRtcKit.framework library.

If you use the SDK of versions ealier than v3.2.0, after integrating the AgoraRtcKit.framework, you also need to integrate the AgoraRtcCryptoLoader.framework encryption library and import the class as follows:

  1. Choose either of the following ways to integrate the encryption library:
Through CocoaPods

a. Ensure that you have installed CocoaPods before the following steps. See the installation guide in Getting Started with CocoaPods. b. In Terminal, go to the project path and run the pod init command to create a Podfile in the project folder. c. Open the Podfile, delete all contents and input the following contents. Remember to change Your App to the target name of your project, and change version to the version of the SDK which you want to integrate.

# platform :ios, '9.0'
target 'Your App' do
pod 'AgoraRtcEngine_iOS_Crypto', '~> version'
end
Copy

d. Go back to Terminal, and run the pod install command to install the Agora SDK. Once you successfully install the SDK, it shows Pod installation complete! in Terminal, and you can see an xcworkspace file in the project folder. e. Open the generated xcworkspace file in Xcode.

Through your local storage

a. Copy AgoraRtcCryptoLoader.framework from the SDK package to the project folder. b. Open Xcode (take the Xcode 11.0 as an example), go to the TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content menu, click Add Other... after clicking + to add AgoraRtcCryptoLoader.framework. To ensure that the signature of the dynamic library is the same as the signature of the app, you need to set the Embed attribute of the dynamic library to Embed & Sign.

According to the requirement of Apple, the Extension of app cannot contain the dynamic library. If you need to integrate the SDK with the dynamic library in the Extension, change the file status as Do Not Embed.

  1. To import the AgoraRtcCryptoLoader library, refer to the following sample code:
// Swift
import AgoraRtcCryptoLoader
Copy
// Objective-C
#import <AgoraRtcCryptoLoader/AgoraRtcCryptoLoader.h>
Copy

2. Generate the key

  1. Refer to the following command to randomly generate a 32-byte key in the string format through OpenSSL on your server.
// Randomly generate a 32-byte key in the string format, and pass the string key in the encryptionKey parameter of enableEncryption.
openssl rand -hex 32
dba643c8ba6b6dc738df43d9fd624293b4b12d87a60f518253bd10ba98c48453
Copy
  1. The client gets the key in the string format from the server and passes it to the SDK in the enableEncryption method.

3. Generate 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.
// 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.
openssl rand -base64 32
X5w9T+50kzxVOnkJKiY/lUk82/bES2kATOt3vBuGEDw=
Copy
  1. The client gets the Base64 salt from the server.

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

4. Enable 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.

  • 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.
  • Sample code

    @interface Server
    + (NSString*) getEncryptionKdfSaltBase64;
    + (NSString*) getEncryptionSecret;
    @end

    @interface Example
    - (BOOL) enableEncryption:(AgoraRtcEngineKit* _Nonnull)engine;
    @end

    @implementation Example
    - (BOOL) enableEncryption:(AgoraRtcEngineKit*)agoraKit
    {
    if(!agoraKit) return NO;
    NSString* encryptionKdfSaltBase64 = [Server getEncryptionKdfSaltBase64];
    NSString* encryptionSecret = [Server getEncryptionSecret];
    if(!encryptionKdfSaltBase64 || !encryptionSecret)
    return NO;

    NSData* encryptionKdfSalt = [[NSData alloc] initWithBase64EncodedString:encryptionKdfSaltBase64 options:0];
    AgoraEncryptionConfig* config = [[AgoraEncryptionConfig alloc] init];
    config.encryptionMode = AgoraEncryptionModeAES128GCM2;
    config.encryptionKey = encryptionSecret;
    config.encryptionKdfSalt = encryptionKdfSalt;
    int result = [agoraKit enableEncryption:YES encryptionConfig:config];
    return (0 == result);
    }
    @end
    Copy

    API reference

    enableEncryption

    Use customized encryption

    Agora provides the IPacketObserver class and the registerPacketObserver method in C++ to enable customized encryption.

    To implement customized encryption, you need to call C++ APIs on iOS or macOS and pay attention to the following:
  • Mix the C++ and Objective-C code in a .mm file.
  • Include the C++ header file at the beginning of the .mm file: #include <AgoraRtcKit/IAgoraRtcEngine.h>.
  • Refer to the following steps to implement customized encryption:

    1. Implement your customized encryption algorithm through the IPacketObserver class in a .mm file.

      class AgoraCustomEncryptionObserver : public agora::rtc::IPacketObserver
      {
      public:
      EVP_CIPHER_CTX *ctx_audio_send;
      EVP_CIPHER_CTX *ctx_audio_receive;
      EVP_CIPHER_CTX *ctx_video_send;
      EVP_CIPHER_CTX *ctx_video_receive;
      AgoraCustomEncryptionObserver()
      {
      m_txAudioBuffer.resize(1024);
      m_rxAudioBuffer.resize(1024);
      m_txVideoBuffer.resize(1024);
      m_rxVideoBuffer.resize(1024);
      }
      virtual bool onSendAudioPacket(Packet& packet)
      {

      int outlen;
      // Encrypts the packet.
      unsigned char outbuf[1024];
      EVP_EncryptInit_ex(ctx_audio_send, EVP_aes_256_gcm(), NULL, NULL, NULL);
      EVP_CIPHER_CTX_ctrl(ctx_audio_send, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      EVP_EncryptInit_ex(ctx_audio_send, NULL, NULL, gcm_key, gcm_iv);
      EVP_EncryptUpdate(ctx_audio_send, outbuf, &outlen, packet.buffer, packet.size);
      // Sends the buffer and size of the encrypted data to the SDK.
      packet.buffer = outbuf;
      packet.size = outlen;
      return true;
      }

      virtual bool onSendVideoPacket(Packet& packet)
      {

      int outlen;
      // Encrypts the packet.
      unsigned char outbuf[1024];
      EVP_EncryptInit_ex(ctx_video_send, EVP_aes_256_gcm(), NULL, NULL, NULL);
      EVP_CIPHER_CTX_ctrl(ctx_video_send, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      EVP_EncryptInit_ex(ctx_video_send, NULL, NULL, gcm_key, gcm_iv);
      EVP_EncryptUpdate(ctx_video_send, outbuf, &outlen, packet.buffer, packet.size);
      // Sends the buffer and size of the encrypted data to the SDK.
      packet.buffer = outbuf;
      packet.size = outlen;
      return true;
      }

      virtual bool onReceiveAudioPacket(Packet& packet)
      {
      int outlen;
      // Decrypts the packet.
      unsigned char outbuf[1024];
      EVP_DecryptInit_ex(ctx_audio_receive, EVP_aes_256_gcm(), NULL, NULL, NULL);
      EVP_CIPHER_CTX_ctrl(ctx_audio_receive, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      EVP_DecryptInit_ex(ctx_audio_receive, NULL, NULL, gcm_key, gcm_iv);
      EVP_DecryptUpdate(ctx_audio_receive, outbuf, &outlen, packet.buffer, packet.size);
      // Sends the buffer and size of the decrypted data to the SDK.
      packet.buffer = outbuf;
      packet.size = outlen;
      return true;
      }

      virtual bool onReceiveVideoPacket(Packet& packet)
      {
      int outlen;
      // Decrypts the packet.
      unsigned char outbuf[1024];
      EVP_DecryptInit_ex(ctx_video_receive, EVP_aes_256_gcm(), NULL, NULL, NULL);
      EVP_CIPHER_CTX_ctrl(ctx_video_receive, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
      EVP_DecryptInit_ex(ctx_video_receive, NULL, NULL, gcm_key, gcm_iv);
      EVP_DecryptUpdate(ctx_video_receive, outbuf, &outlen, packet.buffer, packet.size);
      // Sends the buffer and size of the decrypted data to the SDK.
      packet.buffer = outbuf;
      packet.size = outlen;
      return true;
      }

      private:
      // Sends the audio buffer.
      std::vector<unsigned char> m_txAudioBuffer;
      // Sends the video buffer.
      std::vector<unsigned char> m_txVideoBuffer;

      // Receives the audio buffer.
      std::vector<unsigned char> m_rxAudioBuffer;
      // Receives the video buffer.
      std::vector<unsigned char> m_rxVideoBuffer;
      };
      Copy
    2. Before joining a channel, register the packet observer, so that you can receive events during audio or video packet transmission.

      // Swift
      AgoraCustomEncryption.registerPacketProcessing(agoraKit)
      Copy

      In the .mm file, call registerPacketObserver to register the packet observer.

      + (void)registerPacketProcessing:(AgoraRtcEngineKit *)rtcEngineKit {
      if (!rtcEngineKit) {
      return;
      }
      // Gets the C++ handle of the SDK.
      agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)rtcEngineKit.getNativeHandle;
      // Registers the packet observer.
      rtc_engine->registerPacketObserver(&s_packetObserver);
      s_packetObserver.ctx_audio_send = EVP_CIPHER_CTX_new();
      s_packetObserver.ctx_video_send = EVP_CIPHER_CTX_new();
      s_packetObserver.ctx_audio_receive = EVP_CIPHER_CTX_new();
      s_packetObserver.ctx_video_receive = EVP_CIPHER_CTX_new();
      }
      Copy
    3. Unregister the packet observer after leaving the channel.

      // Swift
      AgoraCustomEncryption.deregisterPacketProcessing(agoraKit)
      Copy

      In the .mm file, call registerPacketObserver(NULL) to unregister the packet observer.

      + (void)deregisterPacketProcessing:(AgoraRtcEngineKit *)rtcEngineKit {
      if (!rtcEngineKit) {
      return;
      }
      // Gets the C++ handle of the SDK.
      agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)rtcEngineKit.getNativeHandle;
      // Unregisters the packet observer.
      rtc_engine->registerPacketObserver(NULL);
      EVP_CIPHER_CTX_free(s_packetObserver.ctx_audio_send);
      EVP_CIPHER_CTX_free(s_packetObserver.ctx_video_send);
      EVP_CIPHER_CTX_free(s_packetObserver.ctx_audio_receive);
      EVP_CIPHER_CTX_free(s_packetObserver.ctx_video_receive);
      }
      Copy

    API reference

    registerPacketObserver

    Voice Calling