转载自:http://bbs.baofengcloud.com/home.php?mod=space&uid=30&do=blog&quickforward=1&id=5
1.加解密原理
1.1.原理
在DH密码系统中,p和g是公开的,发起者和接收者两端的p和g必须相同。特殊地,g等于2,p是一个1024位长的数字。发起者生成一个随机的1024位长的私有数字(x1),以此创建1024位长的DH公开数(y1)。
y1 = g ^ x1 % p
当目标接收到这条消息后。它会生成一个新的1024位随机数作为DH私有数(x2),和1024位的DH公开数(y2)。
y2 = g ^ x2 % p
发起者和反馈者要相互交换y1和y2,然后两端分别计算shared-secret,两端算出的结果应该是一样的。
shared-secret = y1 ^ x2 % p = y2 ^ x1 % p
除了shared-secret,发起者和反馈者两端还需要交换各自的initiator-nonce和responder-nonce,然后就可以计算出各自的加解密秘钥。
decode key = HMAC-SHA256(shared-secret, HMAC-SHA256(responder nonce, initiator nonce))
encode key = HMAC-SHA256(shared-secret, HMAC-SHA256(initiator nonce, responder nonce))
cumulus使用的公开数p。
unsigned char p[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
1.2.握手阶段加解密
handshake阶段,使用默认的sessionid=0,默认的加解密秘钥“Adobe Systems 02”。四部握手的加解密都要使用这个秘钥,但是第四部的时候要使用iikeying协议中获取到的sessionid。
1.3.秘钥交换
秘钥交换指的是交换公开数y1,y2,initiator-nonce和responder-nonce。客户端与服务器端秘钥交换,客户端之间秘钥交换有些区别。certificate和nonce中存放的数据结构都是OPTION。
客户端与服务器端秘钥交换时,y1存放在IIKeying的certificate中,后128字节,y2存放在RIIKeying的nonce中,后128字节。
客户端之间秘钥交换时,y1存放在IIKeying的certificate中,后128字节,y2存放在RIHello的certificate中,后128字节。
2.与服务器交互
2.1.四步握手
2.1.1.IHello
略。
2.1.2.RIHello
certificate无用。
2.1.3.IIKeying
certificate中只有一个option,结构为0x81,0x02,0x1d,0x02 + 128B随机数(y1)。
nonce中包含多个option,结构为0x02,0x1d,0x02,0x41,0x0e + 64B随机数 + 0x03,0x1a,0x02,x0a,0x02,0x1e,0x02。
2.1.4.RIIKeying
nonce中包含多个option,结构为0x03,0x1a,0x00,0x00,0x02,0x1e,0x00,0x41,0x0e + 128B随机数(y2)+ 0x03,0x1a,0x00,0x00,0x02,0x1e,x00。
2.2.建立会话
2.2.1.connect命令
发送包结构
0x80 + flowid + seqnum + fsn + option(0x050,x00,0x54,0x43,0x04
,x00
) + 5B(0x14,0x00,0x00,0x00,0x00) + amf
Name: no-name., STRING: connect
Name: no-name., NUMBER: 1.00
Name: app, STRING:
Name: flashVer, STRING: WIN 11,1,102,55
Name: swfUrl, UNDEFINED
Name: tcUrl, STRING: rtmfp://192.168.200.39
Name: fpad, BOOLEAN: FALSE
Name: capabilities, NUMBER: 235.00
Name: audioCodecs, NUMBER: 3575.00
Name: videoCodecs, NUMBER: 252.00
Name: videoFunction, NUMBER: 1.00
Name: pageUrl, UNDEFINED
Name: objectEncoding, NUMBER: 3.00
返回包结构
0x80 + flowid + seqnum + fsn + option(0x050,x00,0x54,0x43,0x04
,x00;0x02,0x0a,0x02
) + 5B(0x14,0x00,0x00,0x00,0x00) + amf
Name: no-name., STRING: _result
Name: no-name., NUMBER: 1.00
Name: no-name., NULL
Name: level, STRING: status
Name: code, STRING: NetConnection.Connect.Success
Name: description, STRING: Connection succeeded
Name: objectEncoding, NUMBER: 3.00
2.2.2.setpeerinfo命令
发送包结构
0x00 + flowid + seqnum + fsn
+ 5B(0x11,0x00,0x00,0x00,0x00
,0x00
) + amf
Name: no-name., STRING: setPeerInfo
Name: no-name., NUMBER: 0.00
Name: no-name., STRING: 192.168.202.91:1935
返回包结构
0x00 + flowid + seqnum + fsn
+ 7B(
0x0
4,
0x0
0,
0x0
0,
0x0
0,
0x0
0,
0x0
0,
0x
29
) + 4B(server keepalive) + 4B(peer keepalive)
2.3.如果加入group
略
完整流程
3.客户端间交互
3.1四部握手
3.1.1.IHello
略。
3.1.2.FIHello
fihello命令中只有一个initiator地址,但是server可能会观察到initiator多个地址,如redirect中flag=1的地址。如果responder不回应initiator,则server回定时重发fihello命令,其中包含的地址会以轮询的方式变化,第一个fihello中的地址为flag=2的地址。
3.1.3.Redirect
redirect命令中包含多个地址,每个地址前面有个flag,flag=2的地址是客户端自己上报的(准确地址),flag=1的地址为服务器观察到的(可能不准确),我们最好使用flag=2的地址。
flag 2, ip 192.168.202.91, port 58716
flag 1, ip 192.168.66.1, port 58716
flag 1, ip 192.168.108.1, port 58716
flag 1, ip 192.168.202.91, port 58716
3.1.4.RHello
certificate中只有一个option,结构为0x81,0x02,0x1d,0x02 + 128B随机数(y2)。
3.1.5.IIKeying
certificate中只有一个option,结构为0x81,0x02,0x1d,0x02 + 128B随机数(y1)。
nonce中包含多个option,结构为0x02,0x1d,0x02,0x41,0x0e + 64B随机数 + 0x03,0x1a,0x02,x0a,0x02,0x1e,0x02。
3.1.6.RIIKeying
nonce中包含多个option,结构为0x03,0x1a,0x00,0x00,0x02,0x1e,0x00,0x41,0x0e + 64B随机数。
3.2会话建立过程
两个peer之间需要建立两个连接(4个数据流),分别为initiator到responder的连接和responder到initiator的连接,连接的建立过程是相同的,都是由play命令开始的。注意这里的连接值的是NetStream连接,而不是NetConnection连接。NetConnection的连接在整个点播过程中只有1个,而NetStream的连接每两个客户端之间就有2个。一次连接的交互过程如下所示。
play数据包
0x80 + flowid + seqnum + fsn
+ option(0x050,x00,0x54,0x43,0x04
,x05
)
+ 6B(0x11,4B随机
,0x00
) + amf
Name: no-name., STRING: play
Name: no-name., NUMBER: 0.00
Name: no-name., STRING:
data-stream(数据流名字,应用程序觉定)
play确认包
play后面会跟随两个一样的确认包(除了时间戳略有差异),其实一个确认包就够了。
下面的四个chunk有可能是一次性收到的,第一个chunk是0x10,后面三个是0x11,也有可能是分多次收到的。
|RtmpSampleAccess包(这个包可以忽略)
0x80 + flowid + seqnum + fsn + option(0x050,x00,0x54,0x43,0x04
,x02;0x02,0x0a,0x02
) + 6B(0x0f,0x00,0x00,0x00,0x00
,0x00
) + amf
Name: no-name., STRING: |RtmpSampleAccess
Name: no-name., BOOLEAN: FALSE
Name: no-name., BOOLEAN: FALSE
11B包(这个包可以忽略)
0x00 +
11B
观察到的值:0x04,0xc6,0x0c,0xbe,0x16,0x00,0x00,0x00,0x00,0x00,0x02
观察到的值:0x04,0xc6,0x17,0xb6,0x7c,0x00,0x00,0x00,0x00,0x00,0x02
NetStream.Play.Reset包
0x00
+ 6B(0x11,0x00,0x00,0x00,0x00
,0x00
) + amf
Name: no-name., STRING: onStatus
Name: no-name., NUMBER: 0.00
Name: level, STRING: status
Name: code, STRING: NetStream.Play.Reset
Name: description, STRING: Playing and resetting data-stream
NetStream.Play.Start包
0x00
+ 6B(0x11,0x00,0x00,0x00,0x00
,0x00
) + amf
Name: no-name., STRING: onStatus
Name: no-name., NUMBER: 0.00
Name: level, STRING: status
Name: code, STRING: NetStream.Play.Start
Name: description, STRING: Started playing data-stream
NetStream确认包
两个一样的确认包。
整个交互流程可以简化为
3.3.流关联方法
每一个连接建立的过程中需要创建2个数据流。play命令中会带一个flow1,后面跟着的确认包表示flow1建立完成。NetStream命令中会带一个flow2,后面跟着的确认包表示flow2建立完成。我们还需要让flow1和flow2相互关联,方法是在NetStream的OPTION中设置。OPTION有两种,如下所示。
第一个OPTION表示数据流的元信息,具体含义不明。例如:0x050,x00,0x54,0x43,0x04,x05。
第二个OPTION表示当前数据包使用的流与flowID相关联,三个字段都是VLU格式。
在play命令中会带第一种OPTION,NetStream命令中两种OPTION都带,第一个OPTION直接使用play中的OPTION,第二个OPTION中的flowID填写play带过来的flowID,表示两个flow相互关联。
3.4.数据发送
当两边的双向数据流建立完毕后,就可以正式发送数据了,接收端收到的数据与发送端有关系。
发送端调用: sendStream.send("__data", args),第一个参数是要求接收端实现的回调函数名,接收端必须实现这个函数来处理后面跟着的数据。发送端发送的内容就是args字符串。
接收端收到的数据如下:
0x00 + flowid + seqnum + fsn +
6B(0x0
f,0x00,0x00,0x9b,0x70,0x00
) + amf + 6B(0x
11,0x09,0x03,0x01,0x06,0x07
) + 内容
4.部分细节描述
4.1.消息重发
所有用0x10消息扩展的协议都必须由0x51确认,否则就必须走重发流程。
举例:4步握手之后,client会发送由0x10扩展的connect命令,如果server不返回0x51确认,则client会一直重发connect,大约发送了15次,才宣告连接失败。如果server这时候返回了0x51则client就不会重发connect命令了,这时候client会等待server发送connectresult命令,一直等下去。
4.2.handshake过程中重发包
在handshake过程中,initiator给responder发送0x30,0x38,如果responder没有返回包,则会重发,重发间隔类似一个等差数列。responder向initiator发送0x70,如果这时候没有收到后续包不需要重发,重发都是由initiator触发的。
4.3.rtmfp域名访问方式
用rtmfp协议访问域名时,flash会向该域名下所有服务器发送ihello协议,服务器返回rhello协议,flash先收到哪个rhello协议就使用那一台服务器。
4.4.redirect与fihello协议
redirect协议会返回多个打孔被动方地址,fihello只返回打孔主动方一个地址。redirect协议的sessionid=0,fihello的sessionid为客户端与服务器建立的id。