文章结构
**文章结构**基本概念
RTMP Handshake Diagram
**RTMP Handshake Diagram**注: Adobe公布的rtmp协议是个不完整的协议,上图只是一个基本的流程,实际应用时可能存在不一致.
crtmpserver
中的实际握手过程是:**crtmpserver中的实际握手过程** **C1 in C0 + C1** **S2 in S0 + S1 + S2** **S1 in S0 + S1 + S2** **C2**
可以看出其中C2
是Copy of S1
; S2
并不是Copy of C1
.
C1 & S1
The C1 and S1 packets are 1536 octets long.
C1 and S1
的长度均为1536
字节.
-
Time (4 bytes):
This field contains a timestamp, which SHOULD be used as the epoch for all future chunks sent from this endpoint.
This may be 0, or some arbitrary value.
To synchronize multiple chunkstreams, the endpoint may wish to send the current value of the other chunkstream’s timestamp. -
Zero (4 bytes):
This field MUST be all 0s. -
Random data (1528 bytes):
This field can contain any arbitrary values.
Since each endpoint has to distinguish between the response to the handshake it has initiated and the handshake initiated by its peer,this data SHOULD send something sufficiently random.
But there is no need for cryptographically-secure randomness, or even dynamic values.
C1 & S1
有两种Scheme: Scheme 0
& Scheme 1
Scheme 0 & Scheme 1
的区别主要是: key
和digest
的顺序.
HMACSHA256
HMACSHA256是一种验证算法.
基于哈希的消息验证代码 (HMAC) 将密钥与消息数据混合,使用哈希函数对结果进行哈希处理,再次将哈希值与密钥混合,然后第二次应用哈希函数。
输出哈希的长度为 256 位。
握手过程中的客户端验证详解
Handshake 时序图
**Handshake in crtmpserver 时序图**核心函数
InboundRTMPProtocol::PerformHandshake(IOBuffer &buffer, bool encrypted)
中完成了对C0+C1
的处理(如对C1
进行验证),然后形成S0+S1+S2
响应报文.
对客户端发送的C1进行验证
InboundRTMPProtocol::ValidateClient
// 对客户端发送的C1进行验证
bool InboundRTMPProtocol::ValidateClient(IOBuffer &inputBuffer) {
if (_currentFPVersion == 0) {
WARN("This version of player doesn't support validation");
return true;
}
// 先对scheme 0进行验证
if (ValidateClientScheme(inputBuffer, 0)) {
_validationScheme = 0;
return true;
}
// 再对scheme 1进行验证
if (ValidateClientScheme(inputBuffer, 1)) {
_validationScheme = 1;
return true;
}
FATAL("Unable to validate client");
return false;
}
InboundRTMPProtocol::ValidateClientScheme
bool InboundRTMPProtocol::ValidateClientScheme(IOBuffer &inputBuffer, uint8_t scheme) {
uint8_t *pBuffer = GETIBPOINTER(inputBuffer);
uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme);
uint8_t *pTempBuffer = new uint8_t[1536 - 32];
memcpy(pTempBuffer, pBuffer, clientDigestOffset);
memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32,
1536 - clientDigestOffset - 32);
uint8_t *pTempHash = new uint8_t[512];
HMACsha256(pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash);
bool result = true;
for (uint32_t i = 0; i < 32; i++) {
if (pBuffer[clientDigestOffset + i] != pTempHash[i]) {
result = false;
break;
}
}
delete[] pTempBuffer;
delete[] pTempHash;
return result;
}
以scheme 0模式进行举例:
**InboundRTMPProtocol::ValidateClientScheme in scheme 0 mode**
- Step 1.
通过digest
的前四个字节计算出clientDigestOffset
.
图中AC的长度即为clientDigestOffset
.
C
点向后32
字节即为密文即CD
段. - Step 2.
将AC
段和DE
段依次拷贝至临时缓冲pTempBuffer
.
利用pTempBuffer
利用HMACsha256
验证算法进行正向加密,得到
256
字节的密文. - Step 3.
利用Step 2
得到的密文的前32
位与CD
段的密文进行逐字节比较, 如果完全匹配,说明client
端发送的C1
是scheme 0
模式.
实际例子:
**pBuffer[clientDigestOffset]起32个字节** **pTempHash中的密文**
通过对pBuffer[clientDigestOffset]起32个字节和pTempHash的前32字节进行逐字节比较, 发现完全相同, 本次验证通过.
BaseRTMPProtocol::GetDigestOffset
// 获取Digest的offset
uint32_t BaseRTMPProtocol::GetDigestOffset(uint8_t *pBuffer, uint8_t schemeNumber) {
switch (schemeNumber) {
case 0:
{
return GetDigestOffset0(pBuffer);
}
case 1:
{
return GetDigestOffset1(pBuffer);
}
default:
{
WARN("Invalid scheme number: %hhu. Defaulting to 0", schemeNumber);
return GetDigestOffset0(pBuffer);
}
}
}
// scheme 0 获取Digest的offset
uint32_t BaseRTMPProtocol::GetDigestOffset0(uint8_t *pBuffer) {
uint32_t offset = pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11];
offset = offset % 728;
offset = offset + 12;
if (offset + 32 >= 1536) {
ASSERT("Invalid digest offset");
}
return offset;
}
**BaseRTMPProtocol::GetDigestOffset0**
// scheme 1 获取Digest的offset
uint32_t BaseRTMPProtocol::GetDigestOffset1(uint8_t *pBuffer) {
uint32_t offset = pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775];
offset = offset % 728;
offset = offset + 776;
if (offset + 32 >= 1536) {
ASSERT("Invalid digest offset");
}
return offset;
}
**BaseRTMPProtocol::GetDigestOffset1**
References:
https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
https://technet.microsoft.com/zh-cn/library/hh831711.aspx
http://blog.sina.com.cn/s/blog_51396f890102ezcp.html
http://blog.csdn.net/win_lin/article/details/13006803
网友评论