文章結(jié)構(gòu)
基本概念
RTMP Handshake Diagram
注: Adobe公布的rtmp協(xié)議是個(gè)不完整的協(xié)議婶肩,上圖只是一個(gè)基本的流程稠鼻,實(shí)際應(yīng)用時(shí)可能存在不一致.
crtmpserver
中的實(shí)際握手過(guò)程是:可以看出其中C2
是Copy of S1
; S2
并不是Copy of C1
.
C1 & S1
The C1 and S1 packets are 1536 octets long.
C1 and S1
的長(zhǎng)度均為1536
字節(jié).
-
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
的區(qū)別主要是: key
和digest
的順序.
HMACSHA256
HMACSHA256是一種驗(yàn)證算法.
基于哈希的消息驗(yàn)證代碼 (HMAC) 將密鑰與消息數(shù)據(jù)混合,使用哈希函數(shù)對(duì)結(jié)果進(jìn)行哈希處理氏仗,再次將哈希值與密鑰混合,然后第二次應(yīng)用哈希函數(shù)银萍。
輸出哈希的長(zhǎng)度為 256 位返顺。
握手過(guò)程中的客戶端驗(yàn)證詳解
Handshake 時(shí)序圖
核心函數(shù)
InboundRTMPProtocol::PerformHandshake(IOBuffer &buffer, bool encrypted)
中完成了對(duì)C0+C1
的處理(如對(duì)C1
進(jìn)行驗(yàn)證),然后形成S0+S1+S2
響應(yīng)報(bào)文.
對(duì)客戶端發(fā)送的C1進(jìn)行驗(yàn)證
InboundRTMPProtocol::ValidateClient
// 對(duì)客戶端發(fā)送的C1進(jìn)行驗(yàn)證
bool InboundRTMPProtocol::ValidateClient(IOBuffer &inputBuffer) {
if (_currentFPVersion == 0) {
WARN("This version of player doesn't support validation");
return true;
}
// 先對(duì)scheme 0進(jìn)行驗(yàn)證
if (ValidateClientScheme(inputBuffer, 0)) {
_validationScheme = 0;
return true;
}
// 再對(duì)scheme 1進(jìn)行驗(yàn)證
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模式進(jìn)行舉例:
- Step 1.
通過(guò)digest
的前四個(gè)字節(jié)計(jì)算出clientDigestOffset
.
圖中AC的長(zhǎng)度即為clientDigestOffset
.
C
點(diǎn)向后32
字節(jié)即為密文即CD
段. - Step 2.
將AC
段和DE
段依次拷貝至臨時(shí)緩沖pTempBuffer
.
利用pTempBuffer
利用HMACsha256
驗(yàn)證算法進(jìn)行正向加密站绪,得到
256
字節(jié)的密文. - Step 3.
利用Step 2
得到的密文的前32
位與CD
段的密文進(jìn)行逐字節(jié)比較, 如果完全匹配遭铺,說(shuō)明client
端發(fā)送的C1
是scheme 0
模式.
實(shí)際例子:
通過(guò)對(duì)pBuffer[clientDigestOffset]起32個(gè)字節(jié)和pTempHash的前32字節(jié)進(jìn)行逐字節(jié)比較, 發(fā)現(xiàn)完全相同, 本次驗(yàn)證通過(guò).
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;
}
// 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;
}
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