rtmp complex handshake,支持h264/aac

来源:互联网 发布:护眼手机膜 知乎 编辑:程序博客网 时间:2024/06/03 22:53

本文转自win_lin,链接地址:http://blog.csdn.net/win_lin/article/details/13006803 ,然后添加了一些自己的注释

当服务器和客户端的握手是按照rtmp协议进行,是不支持h264/aac的,有数据,就是没有视频和声音。

原因是adobe变更了握手的数据结构,标准rtmp协议的握手的包是随机的1536字节(S1S2C1C2),变更后的是需要进行摘要和加密。

rtmp协议定义的为simple handshake,变更后加密握手可称为complex handshake。

本文详细分析了rtmpd(ccrtmpserver)中的处理逻辑,以及rtmpdump的处理逻辑,从一个全是魔法数字的世界找到他们的数据结构和算法。

complex handshake C1S1结构

complex handshake将C1S1分为4个部分,它们的顺序(schema)一种可能是:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // c1s1 schema0  
  2. time: 4bytes  
  3. version: 4bytes  
  4. key: 764bytes  
  5. digest: 764bytes  

其中,key和digest可能会交换位置,即schema可能是:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // c1s1 schema1  
  2. time: 4bytes  
  3. version: 4bytes  
  4. digest: 764bytes  
  5. key: 764bytes  

客户端来决定使用哪种schema,服务器端则需要先尝试按照schema0解析,失败则用schema1解析。如下图所示:


无论key和digest位置如何,它们的结构是不变的:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 764bytes key结构  
  2. random-data: (offset)bytes  
  3. key-data: 128bytes  
  4. random-data: (764-offset-128-4)bytes  
  5. offset: 4bytes  
  6.   
  7. // 764bytes digest结构  
  8. offset: 4bytes  
  9. random-data: (offset)bytes  
  10. digest-data: 32bytes  
  11. random-data: (764-4-offset-32)bytes  

offset 的计算方法比较特殊,并不是简单的大端序小端序,而是这四个字节累加,然后模上728 = 764-4-32

for(i=0;i<4;i++){offset += CBitsOP::Get8Bits(start,end,TRUE);}offset = offset % 728;


备注:发现FMS只认识digest-key结构。


如下图所示:



crtmp中这些全是魔法数字。

complex handshake C2S2结构

C2S2主要是提供对C1S1的验证。结构如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 1536bytes C2S2结构  
  2. random-data: 1504bytes  
  3. digest-data: 32bytes  

C2S2的结构相对比较简单。如下图所示:


下面介绍C1S1C2S2的生成以及验证算法。

complex handshake C1S1算法

C1S1中的digest都是包含32字节的digest-data,而且digest-data将C1S1分成两部分:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // C1S1被digest分成两部分  
  2. c1s1-part1: n bytes  
  3. digest-data: 32bytes  
  4. c1s1-part2: (1536-n-32)bytes  

如下图所示:



在生成C1时,需要用到c1s1-part1和c1s1-part2这两个部分的字节拼接起来的字节,定义为:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. c1s1-joined = bytes_join(c1s1-part1, c1s1-part2)  

也就是说,把1536字节的c1s1中的32字节的digest拿剪刀剪掉,剩下的头和尾加在一起就是c1s1-joined。

用到的两个常量FPKey和FMSKey:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. u_int8_t FMSKey[] = {  
  2.     0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,  
  3.     0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,  
  4.     0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,  
  5.     0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,  
  6.     0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001  
  7.     0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,  
  8.     0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,  
  9.     0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,  
  10.     0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae  
  11. }; // 68  
  12.   
  13. u_int8_t FPKey[] = {  
  14.     0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,  
  15.     0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,  
  16.     0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,  
  17.     0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001  
  18.     0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,  
  19.     0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,  
  20.     0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,  
  21.     0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE  
  22. }; // 62  

生成C1的算法如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. calc_c1_digest(c1, schema) {  
  2.     get c1s1-joined from c1 by specified schema  
  3.     digest-data = HMACsha256(c1s1-joined, FPKey, 30)  
  4.     return digest-data;  
  5. }  
  6. random fill 1536bytes c1 // also fill the c1-128bytes-key  
  7. time = time() // c1[0-3]  
  8. version = [0x80, 0x00, 0x07, 0x02] // c1[4-7]  
  9. schema = choose schema0 or schema1  
  10. digest-data = calc_c1_digest(c1, schema)  
  11. copy digest-data to c1  

生成S1的算法如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /*decode c1 try schema0 then schema1*/  
  2. c1-digest-data = get-c1-digest-data(schema0)  
  3. if c1-digest-data equals to calc_c1_digest(c1, schema0) {  
  4.     c1-key-data = get-c1-key-data(schema0)  
  5.     schema = schema0  
  6. } else {  
  7.     c1-digest-data = get-c1-digest-data(schema1)  
  8.     if c1-digest-data not equals to calc_c1_digest(c1, schema1) {  
  9.         switch to simple handshake.  
  10.         return  
  11.     }  
  12.     c1-key-data = get-c1-key-data(schema1)  
  13.     schema = schema1  
  14. }  
  15.   
  16. /*generate s1*/  
  17. random fill 1536bytes s1  
  18. time = time() // c1[0-3]  
  19. version = [0x04, 0x05, 0x00, 0x01] // s1[4-7]  
  20. s1-key-data=shared_key=DH_compute_key(peer_pub_key=c1-key-data)  
  21. get c1s1-joined by specified schema  
  22. s1-digest-data = HMACsha256(c1s1-joined, FMSKey, 36)  
  23. copy s1-digest-data and s1-key-data to s1.  

C1S1的算法完毕。

complex handshake C2S2

C2S2的生成算法如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. random fill c2s2 1536 bytes  
  2.   
  3. // client generate C2, or server valid C2  
  4. temp-key = HMACsha256(s1-digest, FPKey, 62)  
  5. c2-digest-data = HMACsha256(c2-random-data, temp-key, 32)  
  6.   
  7. // server generate S2, or client valid S2  
  8. temp-key = HMACsha256(c1-digest, FMSKey, 68)  
  9. s2-digest-data = HMACsha256(s2-random-data, temp-key, 32)  


0 0
原创粉丝点击