兄弟連區塊鏈入門教程以太坊原始碼分析p2p-rlpx節點之間的加密鏈路一
RLPx Encryption(RLPx加密)
之前介紹的discover節點發現協議, 因為承載的資料不是很重要,基本是明文傳輸的。
每一個節點會開啟兩個同樣的埠,一個是UDP埠,用來節點發現,一個是TCP埠,用來承載業務資料。 UDP的埠和TCP的埠的埠號是同樣的。 這樣只要通過UDP發現了埠,就等於可以用TCP來連線到對應的埠。
RLPx協議就定義了TCP連結的加密過程。
RLPx使用了(Perfect Forward Secrecy), 簡單來說。 連結的兩方生成生成隨機的私鑰,通過隨機的私鑰得到公鑰。 然後雙方交換各自的公鑰, 這樣雙方都可以通過自己隨機的私鑰和對方的公鑰來生成一個同樣的共享金鑰(shared-secret)。後續的通訊使用這個共享金鑰作為對稱加密演算法的金鑰。 這樣來說。如果有一天一方的私鑰被洩露,也只會影響洩露之後的訊息的安全性, 對於之前的通訊是安全的(因為通訊的金鑰是隨機生成的,用完後就消失了)。
前向安全性(引用自維基百科)
前向安全或前向保密(英語:Forward Secrecy,縮寫:FS),有時也被稱為完美前向安全[1](英語:Perfect Forward Secrecy,縮寫:PFS),是密碼學中通訊協議的安全屬性,指的是長期使用的主金鑰洩漏不會導致過去的會話金鑰洩漏。[2]前向安全能夠保護過去進行的通訊不受密碼或金鑰在未來暴露的威脅。[3]如果系統具有前向安全性,就可以保證萬一密碼或金鑰在某個時刻不慎洩露,過去已經進行的通訊依然是安全,不會受到任何影響,即使系統遭到主動攻擊也是如此。
迪菲-赫爾曼金鑰交換
迪菲-赫爾曼金鑰交換(英語:Diffie–Hellman key exchange,縮寫為D-H) 是一種安全協議。它可以讓雙方在完全沒有對方任何預先資訊的條件下通過不安全通道建立起一個金鑰。這個金鑰可以在後續的通訊中作為對稱金鑰來加密通訊內容。公鑰交換的概念最早由瑞夫·墨克(Ralph C. Merkle)提出,而這個金鑰交換方法,由惠特菲爾德·迪菲(Bailey Whitfield Diffie)和馬丁·赫爾曼(Martin Edward Hellman)在1976年首次發表。馬丁·赫爾曼曾主張這個金鑰交換方法,應被稱為迪菲-赫爾曼-墨克金鑰交換(英語:Diffie–Hellman–Merkle key exchange)。
迪菲-赫爾曼金鑰交換的同義詞包括:
迪菲-赫爾曼金鑰協商
迪菲-赫爾曼金鑰建立
指數金鑰交換
迪菲-赫爾曼協議
雖然迪菲-赫爾曼金鑰交換本身是一個匿名(無認證)的金鑰交換協議,它卻是很多認證協議的基礎,並且被用來提供傳輸層安全協議的短暫模式中的完備的前向安全性。
描述
迪菲-赫爾曼通過公共通道交換一個資訊,就可以建立一個可以用於在公共通道上安全通訊的共享祕密(shared secret)。
p2p/rlpx.go原始碼解讀
這個檔案實現了RLPx的鏈路協議。
連結聯絡的大致流程如下:
doEncHandshake() 通過這個方法來完成交換金鑰,建立加密通道的流程。如果失敗,那麼連結關閉。
doProtoHandshake() 這個方法來進行協議特性之間的協商,比如雙方的協議版本,是否支援Snappy加密方式等操作。
連結經過這兩次處理之後,就算建立起來了。因為TCP是流式的協議。所有RLPx協議定義了分幀的方式。所有的資料都可以理解為一個接一個的rlpxFrame。 rlpx的讀寫都是通過rlpxFrameRW物件來進行處理。
doEncHandshake
連結的發起者被稱為initiator。連結的被動接受者被成為receiver。 這兩種模式下處理的流程是不同的。完成握手後。 生成了一個sec.可以理解為拿到了對稱加密的金鑰。 然後建立了一個newRLPXFrameRW幀讀寫器。完成加密通道的建立過程。
func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) { var ( sec secrets err error ) if dial == nil { sec, err = receiverEncHandshake(t.fd, prv, nil) } else { sec, err = initiatorEncHandshake(t.fd, prv, dial.ID, nil) } if err != nil { return discover.NodeID{}, err } t.wmu.Lock() t.rw = newRLPXFrameRW(t.fd, sec) t.wmu.Unlock() return sec.RemoteID, nil }
initiatorEncHandshake 首先看看連結的發起者的操作。首先通過makeAuthMsg建立了authMsg。 然後通過網路傳送給對端。然後通過readHandshakeMsg讀取對端的迴應。 最後呼叫secrets建立了共享祕密。
// initiatorEncHandshake negotiates a session token on conn. // it should be called on the dialing side of the connection. // // prv is the local client's private key. func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) { h := &encHandshake{initiator: true, remoteID: remoteID} authMsg, err := h.makeAuthMsg(prv, token) if err != nil { return s, err } authPacket, err := sealEIP8(authMsg, h) if err != nil { return s, err } if _, err = conn.Write(authPacket); err != nil { return s, err } authRespMsg := new(authRespV4) authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn) if err != nil { return s, err } if err := h.handleAuthResp(authRespMsg); err != nil { return s, err } return h.secrets(authPacket, authRespPacket) }
makeAuthMsg。這個方法建立了initiator的handshake message。 首先對端的公鑰可以通過對端的ID來獲取。所以對端的公鑰對於發起連線的人來說是知道的。 但是對於被連線的人來說,對端的公鑰應該是不知道的。
// makeAuthMsg creates the initiator handshake message. func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, token []byte) (*authMsgV4, error) { rpub, err := h.remoteID.Pubkey() if err != nil { return nil, fmt.Errorf("bad remoteID: %v", err) } h.remotePub = ecies.ImportECDSAPublic(rpub) // Generate random initiator nonce. // 生成一個隨機的初始值, 是為了避免重放攻擊麼? 還是為了避免通過多次連線猜測金鑰? h.initNonce = make([]byte, shaLen) if _, err := rand.Read(h.initNonce); err != nil { return nil, err } // Generate random keypair to for ECDH. //生成一個隨機的私鑰 h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) if err != nil { return nil, err }