Bootstrap

WebRTC 传输安全机制第二话:深入显出 SRTP 协议

通过 后,RTC 通信的双方完成 和 的协商。接下来,我们继续分析在 WebRTC 中,如何使用交换的密钥,来对 RTP 和 RTCP 进行加密,实现数据的安全传输。同时,本文会对 libsrtp 使用中,遇到的问题的进行解答,例如,什么是 ROC,ROC 为什么是 32-bits?为什么会返回 error_code=9, error_code=10?交换的密钥有生命周期吗,如果有是多长时间呢?阅读本篇之前建议阅读,两者结合,效果更佳哦!

作者|进学

审校|泰一

要解决的问题

协议并没有对它的负载数据进行任何保护。因此,如果攻击者通过抓包工具,如 Wireshark,将音视频数据抓取到后,通过该工具就可以直接将音视频流播放出来,这是非常恐怖的事情。

在 WebRTC 中,为了防止这类事情发生,没有直接使用 协议,而是使用了 协议 ,即安全的 协议。WebRTC 使用了非常有名的 libsrtp 库将原来的 协议数据转换成 协议数据。

要解决的问题:

  • 对 的负载 (payload) 进行加密,保证数据安全;

  • 保证 包的完整性,同时防重放攻击。

SRTP/SRTCP 结构

SRTP 结构

从 SRTP 结构图中可以看到:

通常情况下只需要对 RTP 负载数据进行加密,如果需要对 RTP header extension 进行加密, 给出了详细方案,在 libsrtp 中也完成了实现。

SRTCP 结构

从 结构图中可以看到:

在初步认识了 和 的结构后,接下来介绍 和 如何得到了。

Key 管理

在 协议中,使用二元组 的方式来标识一个通信参与者的 会话,称为 。

在 SRTP 协议中使用三元组来标识一个 stream,一个 由多个 stream 组成。对每个 stream 的加解密相关参数的描述,称为 。

每个 stream 的 中 中的包含如下参数:

  • SSRC: Stream 使用的 SSRC。

  • Cipher Parameter:加解密使用的 key, salt,算法描述 (类型,参数等)。

  • Authentication Parameter: 完整性使用的 Key, salt,算法描述 (类型,参数等)。

  • Anti-Replay Data: 防止重放攻击缓存的数据信息,例如,ROC,最大序号等。

在 中,每个 Stream 都会使用到属于自己的,加解密 Key,Authentication Key。这些 Key 都是在同一个 Session 中使用到的,称为 。这些 是通过对 使用 KDF (Key Derivation Function) 导出的。

是用于导出 函数,KDF 默认使用是加解密函数。例如,在完成 DTLS 后,协商得到的 SRTP 加密算法的 Profile 为:

SRTP_AES128_CM_HMAC_SHA1_80
         cipher: AES_128_CM
         cipher_key_length: 128
         cipher_salt_length: 112
         maximum_lifetime: 2^31
         auth_function: HMAC-SHA1
         auth_key_length: 160
         auth_tag_length: 80

对应的 为 。 的导出流程如下图所示:

的导出依赖于如下参数:

  • : 根据导出的 Key 的类型不同, 取值如下:

  • master_key: DTLS 完成后,协商得到的 Key。

  • master_salt: DTLS 完成后,协商得到的 Salt。

  • packet_index: RTP/RTCP 的包序号。SRTP 使用 48-bits 的隐式包需要,SRTCP 使用 31-bits 包序号。参考序号管理。

  • key_derivation_rate: key 导出速率,记为 kdr。默认取值为 0,执行 1 次 Key 导出。取值范围 。在 的情况下,在加密之前,执行一次 key 导出,后续在 时,执行 key 导出。

r = packet_index / kdr
key_id = label || r
x = key_id XOR master_salt
key = KDF(master_key, x)

'/':表示整除,B=0 时,C = A/B=0。||:表示连接的含义。A,B,C 使用网络字节序表示,C = A||B, 则 C 的高字节为 A,低字节位为 B。XOR:是异或运算,计算时按照低字节位对齐。

以下使用 ,举例说明 的导出过程,假设 DTLS 协商得到:

master_key:  E1F97A0D3E018BE0D64FA32C06DE4139   // 128-bits
master_salt: 0EC675AD498AFEEBB6960B3AABE6           // 112-bits

导出加密 Key (cipher key):

packet_index/kdr:              000000000000
label:                       00
master_salt:   0EC675AD498AFEEBB6960B3AABE6
-----------------------------------------------
xor:           0EC675AD498AFEEBB6960B3AABE6     (x, KDF input)
x*2^16:        0EC675AD498AFEEBB6960B3AABE60000 (AES-CM input)
cipher key:    C61E7A93744F39EE10734AFE3FF7A087 (AES-CM output)

导出 SALT Key (cipher salt):

packet_index/kdr:              000000000000
label:                       02
master_salt:   0EC675AD498AFEEBB6960B3AABE6
----------------------------------------------
xor:           0EC675AD498AFEE9B6960B3AABE6     (x, KDF input)
x*2^16:        0EC675AD498AFEE9B6960B3AABE60000 (AES-CM input)
               30CBBC08863D8C85D49DB34A9AE17AC6 (AES-CM ouptut)
cipher salt:   30CBBC08863D8C85D49DB34A9AE1

导出校验 Key (auth key),需要 长度为 94 字节:

packet_index/kdr:                000000000000
label:                         01
master salt:     0EC675AD498AFEEBB6960B3AABE6
-----------------------------------------------
xor:             0EC675AD498AFEEAB6960B3AABE6     (x, KDF input)
x*2^16:          0EC675AD498AFEEAB6960B3AABE60000 (AES-CM input)

auth key                           AES input blocks
CEBE321F6FF7716B6FD4AB49AF256A15   0EC675AD498AFEEAB6960B3AABE60000
6D38BAA48F0A0ACF3C34E2359E6CDBCE   0EC675AD498AFEEAB6960B3AABE60001
E049646C43D9327AD175578EF7227098   0EC675AD498AFEEAB6960B3AABE60002
6371C10C9A369AC2F94A8C5FBCDDDC25   0EC675AD498AFEEAB6960B3AABE60003
6D6E919A48B610EF17C2041E47403576   0EC675AD498AFEEAB6960B3AABE60004
6B68642C59BBFC2F34DB60DBDFB2       0EC675AD498AFEEAB6960B3AABE60005

AES-CM 的介绍,参考

至此,我们得到了 加密和认证需要的 :cipher key,auth key,salt key。

序列号管理

SRTP 序列号管理

在 包结构定义中使用 16-bit 来描述序列号。考虑到防重放攻击,消息完整性校验,加密数据,导出 的需要,在 协议中,SRTP 包的序列号,使用隐式方式来记录包序列号 ,使用 i 标识 。

对于发送端来说,i 的计算方式如下:

i = 2^16 * ROC + SEQ

其中,SEQ 是 RTP 包中描述的 16-bit 包序号。ROC (rollover couter) 是 RTP 包序号 (SEQ) 翻转计数,也就是每当 , ROC 计数加 1。ROC 初始值为 0。

对于接收端来说,考虑到丢包和乱序因素的影响,除了维护 ,还需要维护一个当前收到的最大包序号 s_l,当一个新的包到来时候,接收端需要估计出当前包所对应的实际 SRTP 包的序号。ROC 的初始值为 0,s_l 的初始值为收到第一个 SRTP 包的 SEQ。后续通过如下公式,估计接收到的 SRTP 序号 i:

i = 2^16 * v + SEQ

其中,可能的取值,ROC 是接收端本地维护的 ROC,SEQ 是收到 SRTP 的序号。v 分别取 ROC-1,ROC,ROC+1 计算出 i,与 进行比较,那个更接近,v 就取对应的值。完成 SRTP 解密和完整性校验后,更新 ROC 和 s_l,分如下 3 种情况:

更直观的代码描述:

if (s_l < 32768)
    if (SEQ - s_l > 32768)
        set v to (ROC-1) mod 2^32
    else
        set v to ROC
    endif
else
    if (s_l - 32768 > SEQ)
        set v to (ROC+1) mod 2^32
    else
        set v to ROC
    endif
endif
return SEQ + v*65536

SRTCP 序列号管理

中没有描述序号的字段, 的序号在 SRTCP 包,使用 中显示描述,详见 SRTCP 格式,也就是说在 SRTCP 的最大序列号为 2^31。

序列号与通信时长

可以看到 SRTP 的序列号最大值为 2^48, SRTCP 的序列号最大值为 2^16。在大多数应用中(假设每 128000 个 RTP 数据包至少有一个 RTCP 数据包),SRTCP 序号将首先达到上限。以 200 SRTCP 数据包 / 秒的速度, SRTCP 的 2^31 序列号空间足以确保大约 4 个月的通信。

防重放攻击

攻击者将截获的 SRTP/SRTCP 包保存下来,然后重新发送到网络中,实现了包的重放。SRTP 接收者通过维护一个重放列表 (ReplayList) 来防止这种攻击。理论上 Replay List 应该保存所有接收到并完成校验的包的序列号 index。在实际情况下 ReplayList 使用滑动窗口(sliding window)来实现防重放攻击。使用 来描述滑动窗口的大小。

SRTP 防重放攻击

在序列号管理部分,我们详述了接收者,根据接收到的 SRTP 包的 SEQ,ROC,s_l 估算出 SRTP 包的 的方法。同时,将接收者已经接收到 SRTP 包的最大序列号,记为 。计算差值 delta:

delta =  packet_index - local_packet_index

分如下 3 种情况说明:

下图更加直观说明防重放攻击的三个区域:

SRTP-WINDOW-SIZE 的取值,最小是 64。应用可以根据需要设置成较大的值,libsrtp 会向上取整为 32 的整数倍。例如,在 WebRTC 中 。使用者可以根据需要进行调整,但要达到防重放攻击的目的。

SRTCP 防重放攻击

在 SRTCP 中,packet index 显式给出。在 libsrtp 中,SRTCP 的防重放攻击的窗口大小为 128。使用 记录防重放攻击的起始序列号。SRTCP 防重放攻击的检查步骤如下:

加密和校验算法

在 SRTP 中,使用了 CTR(Counter mode)模式的 AES 加密算法,CTR 模式通过递增一个加密计数器以产生连续的密钥流,计数器可以是任意保证长时间不产生重复输出的密钥。根据计数方式的不同,分为以下两种类型:

  • : ICM 模式(Integer Counter Mode,整数计数模式),使用整数计数运算。

  • : GCM 模式(Galois Counter Mode,基于伽罗瓦域计数模式),计数运算定义在伽罗瓦域。

在 SRTP 中,使用 完成加密算法,同时使用 完成 计算,对数据进行完整性校验,加密和 计算需要分两步完成。 基于 AEAD(Authenticated-Encryption with Associated-Data,关联数据的认证加密)的思想,在对数据进行加密的同时计算 值,实现了一个步骤,完成加密和校验信息的计算。下面分别对这个 和 的用法进行介绍。

AEC—ICM

上图描述了 的加密和解密过程,图中的 是通过 KDF 导出的 。加密和加密都是通过对 Counter 进行加密,与明文 P 异或运算得到加密数据 C,反之,与密文 C 异或运算得到明文数据 P。考虑到安全性,Counter 生成依赖于 , 包的索引(packet index)和包的 SSRC。Counter 是 128-bits 的计数,生成方式如下定义:

one byte
<-->
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|00|00|00|00|   SSRC    |   packet index  | b_c |---+
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+   |
                                                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+   v
|                  salt (k_s)             |00|00|->(+)
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+   |
                                                    |
                                                    v
                                            +-------------+
                    encryption key (k_e) -> | AES encrypt |
                                            +-------------+
                                                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+   |
|                keystream block                |<--+
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

其中, 是 Counter 的计数,初始 的取值为 0,对应 Counter 0, 每加密 128-bits 数据, 增加 1,作为下一个 Counter。根据一个 RTP 包的 index,SSRC 计算出来的 Counter,组成了 keystream,每个 Counter 是一个 keystream block。通过使用 算法,对 的负载进行加密得到了 部分。

HMAC—SHA1

散列消息认证码(Hash-based message authentication code,缩写为 HMAC),是一种通过特别计算方式之后产生的消息认证码(MAC),使用密码散列函数,同时结合一个加密密钥,它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证。HMAC 通过一个标准算法,在计算哈希的过程中,把 key 混入计算过程中。HMAC 的加密实现如下:

HMAC(K,M) = H ( (K XOR opad ) + H( (K XOR ipad ) + M ) )

  • H:hash 算法,比如,MD5,SHA-1,SHA-256。

  • B:块字节的长度,块是 hash 操作的基本单位。这里 B=64。

  • L:hash 算法计算出来的字节长度。(L=16 for MD5, L=20 for SHA-1)。

  • K:共享密钥,K 的长度可以是任意的,但是为了安全考虑,还是推荐 K 的长度 > B。当 K 长度大于 时候,会先在 K 上面执行 hash 算法,将得到的 L 长度结果作为新的共享密钥。如果 K 的长度 <, 那么会在 K 后面填充 0x00 一直到等于长度 B。

  • M:要认证的内容。

  • opad:外部填充常量,是 0x5C 重复 B 次。

  • ipad:内部填充常量,是 0x36 重复 B 次。

  • XOR:异或运算。

  • +:代表 "连接" 运算。

计算步骤如下:

和 计算 ,使用的 对应 Key 管理部分描述的 和 ,使用的 Hash 算法为 , 的长度为 80-bits。

在计算 的,要认证的内容 M 为:

M = Authenticated Portion + ROC

其中, 代表 "连接" 运算, 在 的结构图中给出。

在计算 时,要认证的内容 M 为:

M=Authenticated Portion

其中, 在 的结构图中给出。

通过使用 算法,计算得到 的 部分。

AES—GCM

使用计数器模式来加密数据,该操作可以有效地流水线化,GCM 身份验证使用的操作特别适合于硬件中的有效实现。在 详述了 GCM 的理论知识, 详述了硬件实现。

在 加密中的应用,在 进行了详细描述。Key 管理和序列号管理与本文中描述的相同,需要注意的是:

已经实现了 ,有兴趣的同学,可以结合代码进行研读。

libsrtp 的使用

是被广泛使用的 加密的开源项目。经常用到的 api 如下:

重要的结构 ,用来初始化加解密参数,在 中使用这个结构。以下参数需要关注:

是一个新生代实时通信服务器,对 libsrtp 感兴趣的同学,可以快速在本机搭起调试环境,进行相关测试,更加深入理解相关的算法。

总结

本文通过对 相关原理的深入详细解读,对 libsrtp 使用遇到的问题进行解答,希望能够给实时音视频通信的相关领域的同学以帮助。

参考文献