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:
The following diagram describes the encrypted data transmission process:
Agora provides the following open-source sample projects on GitHub.
You can download them and refer to the source code.
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.
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:
Choose either of the following ways to integrate the encryption library:
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
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.
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.
To import the AgoraRtcCryptoLoader library, refer to the following sample code:
// Swift
import AgoraRtcCryptoLoader
// Objective-C
#import <AgoraRtcCryptoLoader/AgoraRtcCryptoLoader.h>
2. Generate the key
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
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
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=
The client gets the Base64 salt from the server.
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.
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.@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
Agora provides the IPacketObserver
class and the registerPacketObserver
method in C++ to enable customized encryption.
.mm
file..mm
file: #include <AgoraRtcKit/IAgoraRtcEngine.h>
.Refer to the following steps to implement customized encryption:
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;
};
Before joining a channel, register the packet observer, so that you can receive events during audio or video packet transmission.
// Swift
AgoraCustomEncryption.registerPacketProcessing(agoraKit)
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();
}
Unregister the packet observer after leaving the channel.
// Swift
AgoraCustomEncryption.deregisterPacketProcessing(agoraKit)
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);
}