流媒体加密

最近更新时间:2022-09-20 05:17:40

功能描述

在实时音视频中,开发者若需要对媒体流加密,SDK 提供内置加密方案,如下:

  1. 内置加密:加密模式和密钥存在于 app 和 SDK 中。

  2. 自定义加密:加密模式和密钥仅存在于 app 中。

通信和直播场景均支持媒体流加密功能。但是在直播场景下,不支持将加密后的媒体流推到 CDN 上。

如需使用媒体流加密功能,请确保接收端和发送端都使用相同的加密方案,否则会出现未定义行为(例如音频无声或视频黑屏)。

下图描述了启用媒体流加密后的数据传输流程:

实现方法

在启用媒体流加密前,请确保已在你的项目中实现基本的实时音视频功能。

使用内置的加密方案

在加入频道前,调用 enableEncryption 方法开启内置加密,并设置加密模式和密钥。

同一频道内所有用户必须使用相同的加密模式和密钥,否则可能导致音频无声或视频黑屏等情况。

SDK 支持 4 种加密模式:

  1. AES_128_XTS: 128 位 AES 加密,XTS 模式。

  2. AES_128_ECB: 128 位 AES 加密,ECB 模式。

  3. AES_256_XTS: 256 位 AES 加密,XTS 模式。

  4. SM4_128_ECB: 128 位国密 SM4 加密,ECB 模式。

示例代码

// 创建一个 EncryptionConfig 对象
EncryptionConfig config = new EncryptionConfig();
// 设置加密模式为国密 SM4 加密模式
config.encryptionMode = EncryptionConfig.SM4_128_ECB;
// 设置加密密钥
config.encryptionKey = "xxxxxxxxxxxxxxxx";
// 启用内置加密
mRtcEngine.enableEncryption(true, config);

使用自定义的加密方案

在各平台上提供了 C++ 的 registerPacketObserver 方法及 IPacketObserver 类,帮助你实现自定义加密功能。参考步骤如下:

  1. 在加入频道前,调用 registerPacketObserver 注册数据包观测器,从而在语音或视频数据包传输时接收事件。

      virtual int registerPacketObserver(IPacketObserver* observer);
    
  2. 实现一个 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;
     };
    
  3. 继承 IPacketObserver,并在你的 app 上使用你自定义的数据加密算法。

    class AnyRTCPacketObserver : public ar::rtc::IPacketObserver
     {
     public:
         AnyRTCPacketObserver()
         {
             m_txAudioBuffer.resize(2048);
             m_rxAudioBuffer.resize(2048);
             m_txVideoBuffer.resize(2048);
             m_rxVideoBuffer.resize(2048);
         }
         virtual bool onSendAudioPacket(Packet& packet)
         {
             int i;
             // 加密数据包
             const unsigned char* p = packet.buffer;
             const unsigned char* pe = packet.buffer+packet.size;
    
             for (i = 0; p < pe && i < m_txAudioBuffer.size(); ++p, ++i)
             {
                 m_txAudioBuffer[i] = *p ^ 0x55;
             }
             // 将加密后数据的缓存地址和缓存大小发回 SDK
             packet.buffer = &m_txAudioBuffer[0];
             packet.size = i;
             return true;
         }
    
         virtual bool onSendVideoPacket(Packet& packet)
         {
             int i;
             // 加密数据包
             const unsigned char* p = packet.buffer;
             const unsigned char* pe = packet.buffer+packet.size;
             for (i = 0; p < pe && i < m_txVideoBuffer.size(); ++p, ++i)
             {
                 m_txVideoBuffer[i] = *p ^ 0x55;
             }
             // 将加密后数据的缓存地址和缓存大小发回 SDK
             packet.buffer = &m_txVideoBuffer[0];
             packet.size = i;
             return true;
         }
    
         virtual bool onReceiveAudioPacket(Packet& packet)
         {
             int i = 0;
             // 解密数据包
             const unsigned char* p = packet.buffer;
             const unsigned char* pe = packet.buffer+packet.size;
             for (i = 0; p < pe && i < m_rxAudioBuffer.size(); ++p, ++i)
             {
                 m_rxAudioBuffer[i] = *p ^ 0x55;
             }
             // 将解密后数据的缓存地址和缓存大小发回 SDK
             packet.buffer = &m_rxAudioBuffer[0];
             packet.size = i;
             return true;
         }
    
         virtual bool onReceiveVideoPacket(Packet& packet)
         {
             int i = 0;
             // 解密数据包
             const unsigned char* p = packet.buffer;
             const unsigned char* pe = packet.buffer+packet.size;
    
             for (i = 0; p < pe && i < m_rxVideoBuffer.size(); ++p, ++i)
             {
                 m_rxVideoBuffer[i] = *p ^ 0x55;
             }
             // 将解密后数据的缓存地址和缓存大小发回 SDK
             packet.buffer = &m_rxVideoBuffer[0];
             packet.size = i;
             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; 
     };
    
  4. 实现一个 java 包装程序。例如:

      JNIEXPORT jint JNICALL Java_org_ar_video_demo_RtcEngineEncryption_enableEncryption(JNIEnv *env, jclass clazz, jlong engineHandle)
    {
       typedef jint (*PFN_registerARPacketObserver)(void* engine, AR ::IPacketObserver* observer);
    
       void* handle = dlopen("libar-rtc-sdk-jni.so", RTLD_LAZY);
       if (!handle)
       {
          return -1;
       }
       PFN_registerARPacketObserver pfn = (PFN_registerARPacketObserver)dlsym(handle, "registerARPacketObserver");
       if (!pfn)
       {
          return -2;
       }
       return pfn((void*)engineHandle, &s_packetObserver);
    }
    
    Java wrapper:
    public class RtcEngineEncryption {
        static {
            System.loadLibrary("ar-encrypt-demo-jni");
        }
        public static native int enableEncryption(long rtcEngineHandle);
    }
    
  5. 调用步骤 4 中实现的 registerARPacketObserverIPacketObserver 类注册一个实例。