32

rtmp handshake | rtmp握手简单模式和复杂模式

 4 years ago
source link: https://www.tuicool.com/articles/uMVfQfa
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

客户端往服务端发送 c0+c1

服务端往客户端发送 s0+s1+s2

客户端往服务端发送 c2

长度

c0和s0都是1字节

c1和c2和s1和s2都是1536字节

两种模式

rtmp握手分两种模式:简单模式和复杂模式

下表是我整理的一些常见rtmp客户端程序、软件使用的握手模式。

名称 模式 obs推流 简单 vlc播放器拉流 复杂 mpv播放器拉流 复杂 ffmpeg推流/拉流 复杂 nginx-rtmp-module推流/拉流 复杂

rtmp服务端正常来说应该两种模式都支持。(不然使用其中一种模式的rtmp客户端可以握手,另一种不行,就尴尬了。。)

本文档主要参照nginx-rtmp-module(以下简称 nrm )的实现以 lal 的实现。

nrm作为rtmp开源服务器,有作为服务端时的握手实现。同时,由于它支持中继的功能,所以也有作为客户端的握手实现。

lal则是我自己使用Go语言写的流媒体服务器,支持rtmp协议。rtmp握手部分的实现是参考nrm写的。内部rtmp客户端握手使用简单模式。rtmp服务端握手两种模式都支持。

lal github地址: https://github.com/q191201771/lal

c0和s0的这个单字节为版本号,简单模式和复杂模式都一样。固定为0x03。

c1和c2和s1和s2在两种模式下格式不一样。

s1可看为c1的回复,c2可看为s2的回复。

nrm中把c0c1和s0s1称为challenge,把c2和s2称为response。

简单模式

可参考 spec-rtmp_specification_1.0.pdf

c0和c1

版本号,固定为0x03

c1和s1

| 4字节时间戳time | 4字节全0二进制串 | 1528字节随机二进制串 |

最前面的4字节时间戳一般以毫秒为单位。

nrm作为客户端时,c1中time使用的是当前unix时间戳的毫秒部分。

nrm作为服务端时,如果判断客户端为简单模式,解析完c1中的时间戳后并没有使用这个时间戳。 发送s1时,是将c1的1536字节原样返回的。

通过4字节二进制串全0,服务端可以判断出是客户端使用的是简单模式。

c2和s2

| 4字节时间戳time | 4字节time2 | 1528字节随机二进制串 |

按文档中的说法:

c2的time应该设置为s1中的time字段。c2的time2应该设置为收到s1的时间点。

s2的time应该设置为c1中的time字段。s2的time2应该设置为收到c1的时间点。

nrm作为服务端时,如果判断客户端为简单模式, 发送s2时,是将c1的1536字节原样返回的。

如果使用obs客户端(obs使用简单握手模式)和nrm服务端握手,你会发现c1、c2、s1、s2的整个1536字节是完全相同的。说明time和time2这些字段,nrm并没有完全按照文档说的来做。

复杂模式

hmac-sha256

介绍复杂模式前,先介绍一个哈希签名算法,即hmac-sha256算法。复杂模式会使用它做一些签名运算和验证。

简单来说,这个算法的输入为一个key(长度可以为任意)和一个input字符串(长度可以为任意),经过hmac-sha256运算后得到一个32字节的签名串。

key和input固定时,hmac-sha256运算结果也是固定唯一的。

c0

固定为0x03

c1

格式如下:

| 4字节时间戳time | 4字节模式串 | 1528字节复杂二进制串 |

time字段参照简单模式下time的说明。

4字节模式串, nrm 使用的是[0x0C, 0x00, 0x0D, 0x0E]。

1528字节复杂二进制串生成规则如下:

步骤一,将1528字节复杂二进制串进行随机化处理。

步骤二,在1528字节随机二进制串中写入32字节的digest签名。

digest的位置

先说明digest的位置如何确定。digest的位置可以在前半部分,也可以在后半部分。

当digest在前半部分时,digest的位置信息(以下简称offset)保存在前半部分的起始位置。

c1格式展开如下:

| 4字节time | 4字节模式串 | 4字节offset | left[...] | 32字节digest | right[...] | 后半部分764字节 |
offset = (c1[8] + c1[9] + c1[10] + c1[11]) % 728 + 12

计算出的offset是相对于整个c1的起始位置而言的。

为什么要取余728呢,因为前半部分的764字节要减去offset字段的4字节,再减去digest的32字节。

为什么要加12呢,是因为要跳过4字节time+4字节模式串+4字节offset。

offset的取值范围为[12,740)。

当offset=12时, left 部分就不存在,当offset=739时, right 部分就不存在。

当digest在后半部分时,offset保存在后半部分的起始位置。

c1格式展开如下:

| 4字节time | 4字节模式串 | 前半部分764字节 | 4字节offset | left[...] | 32字节digest | right[...] |
offset = (c1[8+764] + c1[8+764+1] + c1[8+764+2] + c1[8+764+3]) % 728 + 8 + 764 + 4

计算出的offset依赖是相对于c1的其实位置而言的。

为什么要取余728呢,因为后半部分的764字节要减去offset字段的4字节,再减去digest的32字节。

为什么加8加764加4呢,是因为要跳过4字节time+4字节模式串+前半部分764字节+4字节offset。

offset的取值范围为[776,1504)。

当offset=776时, left 部分就不存在,当offset=1503时, right 部分就不存在。

nrm 作为客户端构造c1时,使用的是第一种格式,即digest放在前半部分。

digest如何生成

说完digest的位置,再说digest如何生成。

即将c1 digest左边部分拼接上c1 digest右边部分(如果右边部分存在的话)作为hmac-sha256的input(整个大小是1536-32),以下大小为30字节固定key作为hmac-sha256的key,进过hmac-sha256计算得出32字节的digest填入c1中digest字段中。

'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
'0', '0', '1',

服务端在收到c1后,首先通过c1中的模式串,初步判断是否为复杂模式,如果是复杂模式,则通过c1重新digest,看计算得出的digest和c1中的包含的digest字段是否相同来确定握手是否为复杂模式。

注意,由于服务端无法直接得知客户端是将digest放在前半部分还是后半部分,所以服务端只能先验证其中一种,如果验证失败,再验证另外一种,如果都失败了,就考虑回退使用简单模式和客户端继续握手。

s0

固定为0x03

s1

s1的构造方法和c1相同。

只不过将模式串换成了 [0x0D, 0x0E, 0x0A, 0x0D]。

并且将hmac-sha256的key换成了如下36字节固定key

'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
'S', 'e', 'r', 'v', 'e', 'r', ' ',
'0', '0', '1',

s2

格式如下:

| 4字节时间戳time | 4字节time2 | 1528字节随机二进制串 |

其中time和time2字段参考简单模式下s2的说明。

1528字节随机二进制串中也需要填入digest。

nrm的做法是 将32字节digest直接填入s2的尾部,也即没有设置相应的offset ,digest的计算方法是,使用digest的左边部分作为hmac-sha256的input(大小是1536-32), 使用c1中的digest作为hmac-sha256的key ,通过hmac-sha256计算得出digest。

c2

c2的构造方法和s2相同。

只不过它是用s2中的digest作为hmac-sha256的key。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK