在实时音视频互动过程中,你需要对媒体流加密,从而保障用户的数据安全。声网提供内置加密和自定义加密方案,你可以根据需要选择合适的加密方案。
下图描述了启用媒体流加密后的数据传输流程:
我们在 GitHub 上提供已实现内置加密功能的开源示例项目。你可以下载体验并参考源代码。
在启用媒体流加密前,请确保已参考《快速开始》文档在你的项目中实现基本的音视频通话或直播功能。
在加入频道前,调用 enableEncryption
方法启用内置加密。
自 v3.4.5 起,声网推荐使用 AES_128_GCM2
或 AES_256_GCM2
加密模式,并设置密钥和盐。
参考以下步骤分别生成并设置密钥和盐。
GCM2
加密模式使用了安全性更高的密钥派生函数且支持盐。如果你选择其他加密模式,则只需设置加密模式和密钥。在你的服务端,参考以下命令通过 OpenSSL 随机生成 String 型、32 字节的密钥。
// 随机生成一个 string 型、32 字节的密钥,并将该密钥传入 enableEncryption 的 encryptionKey 参数。
openssl rand -hex 32
dba643c8ba6b6dc738df43d9fd624293b4b12d87a60f518253bd10ba98c48453
客户端从服务端获取 String 型密钥,并在调用 enableEncryption
时传入 SDK。
在你的服务端,参考以下命令通过 OpenSSL 随机生成 Base64 编码、32 字节的盐。你也可以参考声网在 GitHub 上提供的 C++ 示例代码,在服务端随机生成 byte array 型的盐,然后转换成 Base64 编码。
// 随机生成一个 Base64 编码、32 字节的盐,并将该盐传入 enableEncryption 的 encryptionKdfSalt 参数。
openssl rand -base64 32
X5w9T+50kzxVOnkJKiY/lUk82/bES2kATOt3vBuGEDw=
客户端从服务端获取 Base64 编码的盐。
客户端将盐值从 Base64 编码解码为长度为 32 的 byte[],然后在调用 enableEncryption
时传入 SDK。
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);
}
}
声网提供了 C++ 的 registerPacketObserver
方法及 IPacketObserver
类,帮助你实现自定义加密功能。参考步骤如下:
在加入频道前,调用 registerPacketObserver
注册数据包观测器,从而在语音或视频数据包传输时接收事件。
virtual int registerPacketObserver(IPacketObserver* observer);
实现一个 IPacketObserver
类:
class IPacketObserver
{
public:
struct Packet
{
// 需要发送或接收的数据的缓存地址
const unsigned char* buffer;
// 需要发送或接收的数据的缓存大小
unsigned int size;
};
// 已发送音频包回调
// 在音频包被发送给远端用户前触发
// @param packet 详见: Packet
// @return
// - true: 发送音频包
// - false: 丢弃音频包
virtual bool onSendAudioPacket(Packet& packet) = 0;
// 已发送视频包回调
// 在视频包被发送给远端用户前触发
// @param packet 详见: Packet
// @return
// - true: 发送视频包
// - false: 丢弃视频包
virtual bool onSendVideoPacket(Packet& packet) = 0;
// 收到音频包回调
// 在收到远端用户的音频包前触发
// @param packet 详见: Packet
// @return
// - true: 发送音频包
// - false: 丢弃音频包
virtual bool onReceiveAudioPacket(Packet& packet) = 0;
// 收到视频包回调
// 在收到远端用户的视频包前触发
// @param packet 详见: Packet
// @return
// - true: 发送视频包
// - false: 丢弃视频包
virtual bool onReceiveVideoPacket(Packet& packet) = 0;
};
继承 IPacketObserver
,并在你的 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;
// 加密数据包
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);
// 将加密后数据的缓存地址和缓存大小发回 SDK
packet.buffer = outbuf;
packet.size = outlen;
return true;
}
virtual bool onSendVideoPacket(Packet& packet)
{
int outlen;
// 加密数据包
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);
// 将加密后数据的缓存地址和缓存大小发回 SDK
packet.buffer = outbuf;
packet.size = outlen;
return true;
}
virtual bool onReceiveAudioPacket(Packet& packet)
{
int outlen;
// 加密数据包
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);
// 将解密后数据的缓存地址和缓存大小发回 SDK
packet.buffer = outbuf;
packet.size = outlen;
return true;
}
virtual bool onReceiveVideoPacket(Packet& packet)
{
int outlen;
// 加密数据包
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);
// 将解密后数据的缓存地址和缓存大小发回 SDK
packet.buffer = outbuf;
packet.size = outlen;
return true;
}
private:
// 发送音频数据 buffer
std::vector<unsigned char> m_txAudioBuffer;
// 发送视频数据 buffer
std::vector<unsigned char> m_txVideoBuffer;
// 接收音频数据 buffer
std::vector<unsigned char> m_rxAudioBuffer;
// 接收视频数据 buffer
std::vector<unsigned char> m_rxVideoBuffer;
};
实现一个 java 包装程序。例如:
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");
// 注册数据包观测器
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();
}
调用步骤 4 中实现的 registerAgoraPacketObserver
为 IPacketObserver
类注册一个实例。
registerPacketObserver(nullptr)
方法。