1. 程式人生 > >rtmfp協議分析詳解(一)

rtmfp協議分析詳解(一)

       由於工作的原因需要對rtmfp進行詳細瞭解,網上資源也有很多大部分是英文資源,描述也詳細(可能English功力不夠深厚),更別提RFC文件了寫得也得模糊不清(邊罵還是邊看)。通過大概一週的的時間也算是摸清協議處理流程及協議封包及拆包,自己搭建環境模擬協議通訊過程。下面直接把自己經歷整理出來,讓需要的夥伴少走點歪路,希望對你們有幫助,不足地方也可以指出相互學習。

        rtmfp協議分兩個階段:handshake與session。handshake主要是協商金鑰的一個過程,整個金鑰過程的編碼實現也是利用openssl庫,金鑰是互動金鑰即使你利用抓包工具獲取handshake包也無法破解密碼(加密演算法是開源的,當然你有足夠的功力逆推加密碼演算法,在此膜拜!!)。本文主要針對handshake描述,session主要資料流量傳輸(後期在補上)。

 熟悉Handshake四次握手:

下面圖片來自RFC文件,首先有個初步的瞭解,RFC文件還是必要的https://tools.ietf.org/html/rfc7016

         從上圖可以看出整個握手過程:客戶端傳送IHello-->服務端(回Rhello)-->客戶端(請求IKey)-->服務端(回RKey)。整個過程完也是金鑰的交換,首先算出共享密碼,根據共享金鑰與自己金鑰資訊做hash算出加密與解密金鑰,兩端金鑰只用於當前請求連結之上。注意:服務端解密金鑰只能解客戶發過來的加密包,服務加密金鑰加發給客戶端的資料,同理客戶端也一樣。在分析之前先粗略瞭解下typeID,type欄位為1byte,其中0xFF是不可用,可以根據上圖的描述與下面型別參照對比。type定義如下:

         type               meaning

        0x30           initiator hello
        0x70           responder hello
        0x38           initiator initial keying
        0x78           responder initial keying
        0x0f           forwarded initiator hello
        0x71           forwarded hello response
        0x10           normal user data
        0x11           next user data
        0x0c           session failed on client side
        0x4c           session died
        0x01           reset keepalive request
        0x41           reset keepalive response
        0x5e           negative ack
        0x51           some ack

handshake源始包解包分析:

        此過程很重要主要熟悉協議格式,我用C程式碼自己封裝與解包,有需要的夥伴可以參考下,當然還有開源的https://github.com/OpenRTMFP/Cumulus (C++),不管是閒得蛋疼還是工作需要我還是用C重寫。
        在看RFC文件時會提到handshake過程使用的預設公共16位金鑰"Adobe Systems 02",客戶端與服務端在金鑰協商之前都是用該公共金鑰加解密。

                      Hex: 41 64 6F 62 65 20 53 79 73 74 65 6D 73 20 30 32

         注意:加密與解密需要偏移4btyes(這四位是session_id,此過程session_id經過計算是等於0,計算過程下面會提到)。下面就使用公共金鑰做測試,以下內容是自己手抓包分析。

1、第一次握手:IHello    (客戶端請求)

      initiator hello包payload格式如下:
                    initiator-hello payload = unknow| epd length |epd type| epd value| tag

         上圖是從愛奇藝抓包本場編碼解析分析,可看出前4bytes(sessionID)已經擷取,從圖片上來看前2bytes是checksum,第3個byte是Marker,第4,5兩個btyes為timesent,第6個bytes為type型別(第一個方格),第7,8兩個bytes接下來有效包長度(第二個方格),從第三個方格以下內容屬於initiator-hello payload內容。0x2a-unknow,0x29-epd長度, 0x0a-epd type(rtmfp url),接下是rtmfp url內容,最後一條紅線標識是16bytes tags(16位隨機數,tag在下次互動是需要帶上)。最後注意圖片0xff是填充數(這裡其實是13個0xff),這裡主要是因為加密使用16位對稱加密,必須滿足資料包的長度是能被16整除(sessionID的4個bytes不包含在內)。epdtype是指明當前互動模式,下面簡單描述下:

                epd type有兩個合法值:
                 0x0a:client-server模式,epd value是想要連線的server的rtmfp url。
                 0x0f :peer-to-peer模式,epd value是想要連線的client的peer id,一般是固定的32位元組。

2、第二次握手:Rhello(服務端回覆)

      responder hello包payload格式如下:
               payload = tag-echo | cookie | responder-certificate

        在上圖前面8bytes可以參考第一次握手分析過程,這裡直接分析內容在上圖第三個方格及時IHello請求tag長度,接下是tag內容原樣攜帶。第四個方格為cookie長度(一般是固定64位隨機數),接下來是cookie內容。在接下來的是服務端面證書內容是固定77個bytes,以下對responder-certificate做格式分析:

         certificate= \0x01\0x0A\0x41\0x0E | dh-public-num | \0x02\0x15\0x02\0x02\0x15\0x05\0x02\0x15\0x0E

         其中dh-public-num是一個64 byte隨機數。(這裡responder-certificate理解為responder-component會更好理解,很多文件雖然都是用responder-certificate表示服務端     面證書,但我個人感覺容易與下面initiator-certificate混淆。對服務端而言理解為本地臨時資料更好些)這個很重要,很重要,很重要……!客戶端加解密時需要。)
避免與RFC文件混淆,現改RFC文件標準描述“responderCertificate”,同時之前描述有錯誤,Rhello在C-S模式下無用。

3、第三次握手:IIKey(客戶端請求)

      initiator initial keying包的payload格式如下:
               payload = initiator-session-id | cookie-echo | initiator-certificate |sessionkey-initiator-component | 'X'

         在上圖前面8bytes可以參考第一次握手分析過程,第三個方格為客戶端發過來的臨時sessionID,第四個方格為RHello包中的cookie長度緊接著的是cookie內容(注意是RHello包中原樣cookie)。第五個方格為客戶端證書長度,緊接著是證書內容,其中證書長度(如0x81 04 表示 84,即最高位表示是否還有下一位元組,write_7bit_variable_length函式實現過程,後面呈現)。第六個方格客戶端證書長度,緊接著產76個bytes證書。

         initiator-certificate = \0x81\0x02\0x1d\0x02|dh-public-num      

         其中dh-public-num是128bytes由DH_generate_key生成。

         sessionkey-initiator-component=\x02\x1D\x02\x41\x0E|initiator-component|\x03\x1A\x02\x0A\x02\x1E\x02

         其中initiator-component是一個64 byte隨機數。(跟RHello中的responder-certificate一樣)。

          重點來了,服務端計算金鑰方式:(peer_id, shared_key, encode_key,decode_key,為了與客戶端的金鑰計算分開,此過程需要用到Rikey的sessionKeyResponderComponent包部分)

                   peer_id:服務端是根據initiator-certificate做EVP_sha256計算出來,這個id在網路中是唯一。(這裡有疑問:既然是根據客戶端的資料生成"絕對不能是唯一",這點說法沒錯,我只能說這個是概率性的問題,從這裡可以看出伺服器沒有做peerid的管理)

                  shared_key:服務端收到客戶端的initiator-certificate,用DH_compute_key計算出來的值。(其中該函式DH正好是下次握手DH_generate_key生成的128bytes證書,理解詳細過程可以看openssl原碼,這裡大概講下,用DH_generate_key函式會生成一個public_key與private_key,用public_key作certificate發給對方,在用DH_compute_key計算時用自己的private_key與對方的certificate,計算出來的值就是雙方共享key,這個key雙方計算出來的值一定是一樣的。計算shared_key時需要去前4個bytes。如果還是有點模糊,直接粘程式碼供參考。)

        encode_key:此時服務端需要預先Rikey包中的sessionKeyResponderComponent資料。(該包RIKey做分析)

        公式:encode_key = HMAC-SHA256(shared-secret, HMAC-SHA256(sessionkey-initiator-component,sessionKeyResponderComponent))。

       decode key:由本地證書與對端臨時資料(即IIKey中sessionkey-initiator-component)在與shared_key做256雜湊算出來的值。

        公式:decode key = HMAC-SHA256(shared-secret, HMAC-SHA256(sessionKeyResponderComponent, sessionkey-initiator-component))。

                   這個公式根據我測試驗證的結果稍做描述便於理解,responder nonce====>local certificate(服務端自己生成的證書,RIKey是需要給客戶端),initiator nonce====>far component(即responder's-component的值)。

        到這裡服務端已經算出此次握手過程的金鑰,但現階段還是handshake在回覆RIKey時還是需要用公共金鑰。(計算出來的金鑰是32bytes,只需要用前16bytes做最終金鑰值)。

4、第四次握手:RIKey(服務端回覆)

     responder initial keying的payload的格式如下:
              payload = responder session id | responder’s-component | ‘X’

        在上圖前面8bytes可以參考第一次握手分析過程,第三個方格為客戶端發過來的sessionID,後面所有服務端的sessionID都是這個。第四個方格為資料包長度(證書內容,同樣最高位表示是否還有下一位元組),接下來服務端的證書(由第三次握手時服務DH_generate_key生成的128bytes證書)。此時客戶端已經得到所有能計算金鑰的元素(shared_key, encode_key,decode_key)。

                 shared_key:客戶端收到服務端component,即responder’s component。計算方法跟IIKey方式一樣,用DH_compute_key計算出來的128bytes值。(這裡補充一點,在計算shared_key時需要去掉證書頭,只取證書內容128bytes的隨機值,例如:IIKey中initiator-component 需要去掉前4個bytes,RIKey中responder’s component需要去掉前11個bytes)。

          decode_key:客戶端的initiator-component與服務端responder-component。

         公式:decode key = HMAC-SHA256(shared-secret, HMAC-SHA256(sessionKeyInitiatorComponent, sessionKeyResponderComponent))

         encode_key:服務端responder-component與客戶端的initiator-component。

          公式:encode key = HMAC-SHA256(shared-secret, HMAC-SHA256(sessionKeyResponderComponent, sessionKeyInitiatorComponent))。

                  responder nonce====>far certificate(伺服器的證書),initiator nonce===>local component(即IIKey中responder's-component的值)。

                  此過程完成後,雙方都持有對方的解密金鑰與本地加密金鑰。

5、驗證:
        經過以上四次握手金鑰互動已經完成,相信很多人第一次自己封包與解包的時候應該都會出錯,作為金鑰交換首先明確一點,自己的加密金鑰應該是對方的解密金鑰,不然你加密的資料對方用另外一個金鑰解密是絕對解不出來的,本著這個思想去驗證。shared_key應該在本次金鑰交換過程雙方都一致。到這裡相信大家對該協議已經不陌生,恰好這還只是rtmfp協議的開始,其中session資料流格式,P2P模式還待研究,望同道中人貢獻你的愛心,一起成長。續篇我貼出相關程式碼供參考。

參考:
http://blog.csdn.net/linyanwen99/article/details/8664626 (協議格式比較詳細)
http://www.voidcn.com/blog/charleslei/article/p-5010155.html (Handshake比較詳細)