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 an open-source sample project that implements built-in encryption on GitHub. You can try the demo and view 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.
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.
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.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.
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 a byte[] of length 32, and then passes it to the SDK in the enableEncryption
method.
import java.util.Base64;
import io.agora.rtc.RtcEngine;
class Example
{
public bool enableEncryption(RtcEngine engine) {
if(engine == null)
return false;
String encryptionKdfSaltBase64 = Server.getEncryptionKdfSaltBase64();
String encryptionSecret = Server.getEncryptionSecret();
if(encryptionKdfSaltBase64 == null || encryptionSecret == null)
return false;
byte[] encryptionKdfSalt = Base64.getDecoder().decode(encryptionKdfSaltBase64);
EncryptionConfig config = new EncryptionConfig();
config.encryptionMode = EncryptionConfig.EncryptionMode.AES_128_GCM2;
config.encryptionKey = encryptionSecret;
System.arraycopy(encryptionKdfSalt, 0, config.encryptionKdfSalt, 0, config.encryptionKdfSalt.length);
int result = engine.enableEncryption(true, config);
return (result == 0);
}
}
To implement the customized encryption, use IPacketObserver
class and registerPacketObserver
in C++ as follows:
Before joining a channel, call registerPacketObserver
to register the packet observer, so that you can receive events during audio or video packet transmission.
virtual int registerPacketObserver(IPacketObserver* observer);
Implement an IPacketObserver
class.
class IPacketObserver
{
public:
struct Packet
{
// Buffer address of the sent or received data.
const unsigned char* buffer;
// Buffer size of the sent or received data.
unsigned int size;
};
// Occurs when the local user sends an audio packet.
// The SDK triggers this callback before the audio packet is sent to the remote user.
// @param packet See Packet.
// @return
// - true: The audio packet is sent successfully.
// - false: The audio packet is discarded.
virtual bool onSendAudioPacket(Packet& packet) = 0;
// Occurs when the local user sends a video packet.
// The SDK triggers this callback before the video packet is sent to the remote user.
// @param packet See Packet.
// @return
// - true: The video packet is sent successfully.
// - false: The video packet is discarded.
virtual bool onSendVideoPacket(Packet& packet) = 0;
// Occurs when the local user receives an audio packet.
// The SDK triggers this callback before the audio packet of the remote user is received.
// @param packet See Packet.
// @return
// - true: The audio packet is sent successfully.
// - false: The audio packet is discarded.
virtual bool onReceiveAudioPacket(Packet& packet) = 0;
// Occurs when the local user receives a video packet.
// The SDK triggers this callback before the video packet of the remote user is received.
// @param packet See Packet.
// @return
// - true: The video packet is sent successfully.
// - false: The video packet is discarded.
virtual bool onReceiveVideoPacket(Packet& packet) = 0;
};
Inherit the IPacketObserver
class and use your customized encryption algorithm on your app.
class AgoraRTCPacketObserver : 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;
AgoraRTCPacketObserver()
{
__android_log_print(ANDROID_LOG_INFO, "agoraencryption", "AgoraRTCPacketObserver0");
m_txAudioBuffer.resize(1024);
m_rxAudioBuffer.resize(1024);
m_txVideoBuffer.resize(1024);
m_rxVideoBuffer.resize(1024);
__android_log_print(ANDROID_LOG_INFO, "agoraencryption", "AgoraRTCPacketObserver1");
}
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;
};
Implement a Java wrapper. You can refer to the following example:
JNIEXPORT void JNICALL
Java_io_agora_api_streamencrypt_PacketProcessor_doRegisterProcessing
(JNIEnv *env, jobject obj)
{
__android_log_print(ANDROID_LOG_INFO, "agoraencryption", "doRegisterProcessing0");
if (!rtcEngine) return;
__android_log_print(ANDROID_LOG_INFO, "agoraencryption", "doRegisterProcessing1");
// Registers the packet observer.
int code = rtcEngine->registerPacketObserver(&s_packetObserver);
__android_log_print(ANDROID_LOG_INFO, "agoraencryption", "%d", code);
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();
}
Call registerAgoraPacketObserver
implemented in step 4 to register the IPacketObserver
instance.
registerPacketObserver(nullptr)
method after leaving the channel.