Rtmp协议复杂握手(handshake)详解

Rtmp协议复杂握手(handshake)详解

一、复杂握手流程图

二、过程详解

先从Wireshark抓包中直观的认识握手到底长什么样子吧

1、Client->Server:C0+C1

格式:

C0:一个字节0x03,

C1:timestamp(4bytes)+ Version(4bytes)+ (复杂二进制串)1526bytes

  • timestamp(4bytes):以后所有从该终端发送的块都应该以该时刻为参考点。时间戳取值可以为零或其他任意值。为了同步多个块流,终端可能希望发送其他块流的时间戳。
  • Version(4bytes):至于这个Version具体是多少因客户端的不同也是不同。在srs中给的是

version = [0x80, 0x00, 0x07, 0x02] // c1[4-7]

version = [0x04, 0x05, 0x00, 0x01] // s1[4-7]

我也是按Srs中给的使用的。

  • 复杂二进制串(4bytes):又分为两种情况schema0与schema1(),如下:
  • c1s1 schema0 :key在前,digest在后
  • time: 4 bytes
  • version: 4 bytes
  • key: 764 bytes
  •  digest: 764 bytes

 

  • c1s1 schema1:digest在前,key在后
  • time: 4 bytes
  • version: 4 bytes
  • digest: 764 bytes
  • key: 764 bytes

注意:在抓包分析中FMS用的digest-key模式(schema1),从各大文章中分析得出的貌似FMS仅支持schema1模式

下后面详细讲解复杂二进制串的构成与算法

2、Server->Client:S0+S1+S2

格式:

S0:一个字节0x03,

S1:timestamp(4bytes)+ Version(4bytes)+ (复杂二进制串)1526bytes

S2:timestamp(4bytes)+ timestamp2(4bytes)+ (复杂二进制串)1526bytes

S1结构与C1结构完全相同,S2中只是把Version换成了timestamp2。

本人在实现rtmp服务的过程中发现,不论是srs还是crtmpserver这个Version都不同,个人尝试只要不是0都是可以的。

S1的时间戳作为服务器我用的当前时间戳,不管事FMS还是SRS生成的S1时间戳分析出来都是一个以前的时间戳甚至是一八几几年的时间,我也是很无语。有大神知道这个逻辑请指教。

3、Client->Server:C2

格式:

C2:timestamp(4bytes)+ timestamp2(4bytes)+ (复杂二进制串)1526bytes

S1结构与S2结构完全相同。

注意:在C2、S2中出现的时间戳的计算,如下:

     Time(4个字节): 这个字段必须包含终端在S1 (给 C2) 或者 C1 (给 S2) 发的 timestamp。

     Time2 (4个字节):这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。

4、复杂二进制字符串的构成与算法

//1C、S1结构 key:764bytes 
random data:(offset)bytes    
key data:(128bytes) --可以秘钥
random data(764-offset-128-4)bytes
offset:4bytes(大端对应在0-764的正整数)

//digest: 764bytes 
offset: 4bytes
random-data: (offset)bytes
digest-data: 32bytes
random-data: (764-4-offset-32)bytes

C1、S1结构图

1、offset

找到offset那四个字节分别转换成数字相加,因为这个是随机数有可能超过了key的所有长度所有需要取余数。这个取余的数就是764字节的整体长度- 128字节的Key秘钥-4字节的自身长度。具体算法如下:

var keyMaxOffsetSize = 764 - 128 - 4;  //632
int keyOffset;
switch (schemal)
{
    case SchemalType.schemal0: //key在后 
        keyOffset = (data[1536 - 1] + data[1536 - 2] + data[1536 - 3] + data[1536 - 4]) % keyMaxOffsetSize + 8 + 764;
        return keyOffset;
    case SchemalType.schemal1:  //key在前
        keyOffset = (data[8 + 760] + data[8 + 1 + 760] + data[8 + 2 + 760] + data[8 + 3 + 760]) % keyMaxOffsetSize + 8;
        return keyOffset;
}

友情提示:这个offset的数值即是Key秘钥的索引。

2、key秘钥

这个就是128字节长度的二进制串,rtmp服务器在收到C1的key秘钥后,会生成S1的秘钥key。在官方及其各大博客中介绍的都是OpenSSL中的一个加密算法传入C1的key秘钥从而得到共享秘钥,把这个共享秘钥作为S1的key秘钥:

在srs中是这样使用的:

BIGNUM* ppk = NULL;

if ((ppk = BN_bin2bn((const unsigned char*)ppkey, ppkey_size, 0)) == NULL) {

       ret = ERROR_OpenSslGetPeerPublicKey;

       return ret;

}

 

但是:在C#中很难去实现这个算法,后来我发现如果把C1的秘钥key直接当做S1秘钥key发给客户端也是可以的。

3、digest

digest是通过秘钥算出来的一个32字节的二进制串(注意:我这里写的是秘钥,而不是秘钥key)。

在求digest的过程中用到几个秘钥,如下:

 

public static byte[] FMSKey = new byte[]{
            0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
            0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
            0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
            0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
            0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
            0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
            0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
            0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
            0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
        }; // 68

        public static byte[] FPKey = new byte[] {
            0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
            0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
            0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
            0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
            0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
            0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
            0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
            0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
        }; // 62 

其中还设计到一个算法:HmacSHA256算法,如下:

/// <summary>
/// HmacSHA256算法,返回的结果始终是32位
/// </summary>
/// <param name="key">加密的键,可以是任何数据</param>
/// <param name="keySize">取key数据的长度</param>
/// <param name="content">待加密的内容</param>
/// <returns>返回base64编码字符串</returns>
public static byte[] HmacSHA256(byte[] key, int keySize, byte[] content)
{
    var srsKey = new byte[keySize];
    Array.Copy(key, 0, srsKey, 0, keySize);
    using (var hmacsha256 = new HMACSHA256(srsKey))
    {
        byte[] hashmessage = hmacsha256.ComputeHash(content);
        return hashmessage;
    }
}

另一个内容就是在计算digest所使用的输入参数,该参数是整个1536字节取出36字节digest部分所剩余部分,在此命名为joinedArray,joinedArray结构图如下

 

S1 digest算法如下:

获取到digest的offset,然后求出joinedArray

var Digest = HmacSHA256(FMSKey, 36, joinedArray);

 

注意:C1的schema模式要与S1的模式相同,所以在生成S1的时候要先求出C1的模式(一般是schem1)与key秘钥。C1的验证公式为:

if (Convert.ToBase64String(HmacSHA256(FPKey, 30, joinedArray)) == Convert.ToBase64String(c1.digest))
{
    return true;
}

C2、S2结构

C2、S2结构 1536bytes
time:4bytes
time2:4bytes
random-data: 1536-4-4-32 = 1496bytes
digest-data: 32bytes

 

其实C2、S2就是把digest放到最后那32字节上。

C2、S2digest算法如下:

第一步:计算一个临时key,var temp_key = HmacSHA256(FMSKey, 68, c1.Digest);

第二部:计算joinedArray

第三部:使用临时kye计算digest,var digest = HmacSHA256(temp_key, 32, joinedArray);

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值