以太坊RLPx傳輸協議
- 2019 年 10 月 3 日
- 筆記
本文主要內容翻譯自:The RLPx Transport Protocol,其中添加了一些個人的理解,由於密碼學水平有限,不正確之處望指正。另外原文可能已經更新,最新內容請直接閱讀原文。
本文檔定義了RLPx傳輸協議,一種基於TCP的用於Ethereum節點間通信的傳輸協議。該協議適用於任意內容的加密幀,但它通常用於承載devp2p應用程序協議。
節點標識
所有加密操作都基於secp256k1
橢圓曲線。每個節點都需要維護一個在會話間保存和復原的靜態私鑰。建議私鑰只能手動重置,例如,通過刪除文件或數據庫條目。
ECIES加密
ECIES(Elliptic Curve Integrated Encryption Scheme)是在RLPx握手中使用的非對稱加密方法。RLPx使用的密碼系統是:
secp256k1
橢圓曲線,生成元(G)- (KDF(k,len)):
NIST SP 800-56
級聯密鑰導出函數 - (MAC(k,m)):
HMAC
使用SHA-256
哈希函數 -
(AES(k,iv,m)):
AES-256
加密算法CTR
模式(這裡原文是
AES-128
,但是Ethereum源代碼中是AES-256
,所以本文更改為AES-256
)
Alice想要發送可以被Bob的靜態私鑰(k_B)解密的加密消息。Alice知道Bob的靜態公鑰(K_B)。
為了加密消息(m),Alice生成了一個隨機數(r),相應的生成了橢圓曲線公鑰(R=r*G)並計算出共享密鑰(S=P_x),((P_x,P_y)=r*K_B)。接着,(k_Emidmid k_M=KDF(S,32))導出加密和認證主密鑰,隨機生成一個初始向量(iv)。Alice向Bob發送加密消息(R midmid iv midmid c midmid d),其中(c=AES(k_E,iv,m)),(d=MAC(k_M,ivmidmid c))。
Bob需要解密消息(R midmid iv midmid c midmid d),為此,需要導出共享密鑰(S=P_x),其中((P_x,P_y)=k_B*R),以及導出加密和認證密鑰(k_E midmid k_M=KDF(S,32))。Bob通過等式(d==MAC(k_M,ivmidmid c))是否成立驗證消息後計算(m=AES(k_E,ivmidmid c))獲取明文消息。
握手
「握手」過程構建了會話階段中使用的主密鑰。握手是在發起端(發起TCP連接請求的節點)和接收端(接受連接的節點)之間完成。
握手協議:
E
是上面定義的ECIES
非對稱加密函數。
補充說明: E代表加密;S代表簽名;H代表Hash運算
auth -> E(remote-pubk, S(ephemeral-privk, static-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0) # 由握手發起方發送,向對方發送密鑰協商需要的本節點(公鑰+臨時公鑰+隨機數) auth-ack -> E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) # 接收方回應auth消息,向對方發送密鑰協商需要的本節點(臨時公鑰+隨機數),本節點公鑰對方已經知道,所以這裡不需要發送了。 static-shared-secret = ecdh.agree(privkey, remote-pubk)
握手後值的計算(步驟如下):
ephemeral-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) shared-secret = keccak256(ephemeral-shared-secret || keccak256(nonce || initiator-nonce)) aes-secret = keccak256(ephemeral-shared-secret || shared-secret) # destroy shared-secret mac-secret = keccak256(ephemeral-shared-secret || aes-secret) # destroy ephemeral-shared-secret Initiator: egress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-sent-init) # destroy nonce ingress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-recvd-ack) # destroy remote-nonce Recipient: egress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-sent-ack) # destroy nonce ingress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-recvd-init) # destroy remote-nonce
- 用臨時密鑰,一定程度上可以保證前向安全性。後文中有前向安全性的描述。
- 握手過程最重要的是協商密鑰(對應上面的aes-secret、mac-secret)。
- 密鑰協商過程中需要知道本節點和對方節點的(公鑰+臨時公鑰+隨機數)。這不是絕對的,不同的密鑰協商算法有不同的實現方式,但基本上都需要雙方交換一些數據,從而分別推到出相同的密鑰。
補充一點,整個協商密鑰的過程核心是ECDH密鑰協商,但ECDH協商的過程前提是要對方是認證過的,可信的,前面的ECIES加密,實際上相當於對消息接收方進行認證,因為只有擁有對應的私鑰才能解密消息。
創建加密連接主要流程如下:
- 發起端向接收端發起TCP連接,發送
auth
消息 - 接收端接受連接,解密、驗證
auth
消息(檢查recovery of signature == keccak256(ephemeral-pubk)
) - 接收端生成
auth-ack
消息 - 接收端導出密鑰,發送第一個數據幀
- 發起端接收到
auth-ack
消息,導出密鑰 - 發起端發送第一個數據幀(代碼中對應Hello packet)
- 接收端接收並驗證數據幀
- 發起端接收並驗證數據幀
- 如果
MAC
兩邊都驗證通過,加密握手完成。
簡單概括就是:建立TCP連接–>密鑰協商(auth、auth-ack)–>雙發導出相同的密鑰–>發送hello(協議協商)–>創建完成。具體實現需要看Ethereum源碼。
分幀
在auth
之後的所有數據包都是分幀傳輸的。任何一方如果第一幀數據包驗證失敗都可以斷開連接。
分幀傳輸的主要目的是在單一連接上可靠的支持多路復用協議。其次,因數據包分幀,為消息認證碼產生了適當的分界點,使得加密流變得簡單了。數據幀通過握手產生的密鑰進行驗證。
數據幀頭部提供了數據包的大小和協議信息。
frame = header || header-mac || frame-data || frame-mac header = frame-size || header-data || padding frame-size = size of frame excluding padding, integer < 2**24, big endian header-data = rlp.list(protocol-type[, context-id]) protocol-type = integer < 2**16, big endian context-id = integer < 2**16, big endian padding = zero-fill to 16-byte boundary frame-content = any binary data header-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ header-ciphertext).digest frame-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ left16(egress-mac.update(frame-ciphertext).digest)) egress-mac = keccak256 state, continuously updated with egress bytes ingress-mac = keccak256 state, continuously updated with ingress bytes left16(x) is the first 16 bytes of x || is concatenate ^ is xor
對發送與接收的密文數據不斷更新egress-mac
或ingress-mac
實現消息認證;對頭部數據,是通過將加密輸出數據的頭部與相應的mac
進行異或運算(參見header-mac
)。這樣做是為了確保對明文mac
和密文執行統一操作。所有的mac都是以明文形式發送的。
填充位元組用於防止緩存區飢餓,使得幀組件按指定區塊位元組大小對齊。
已知的問題
- RLPx握手被認為是易破解的,因為
aes-secret
和mac-secret
被重複用於讀取和寫入
。RLPx連接的兩端從相同的密鑰,nonce和IV生成兩個CTR流。如果攻擊者知道一個明文,他們就可以用重用的密鑰流破解未知明文。 - 幀編碼提供了用於多路復用的協議類型字段
protocol-type
,但devp2p未使用該字段。
RLPx傳輸協議的前向安全性
RLPx使用了(PerfectForwardSecrecy),簡單來說。鏈接的兩方都生成隨機的私鑰,通過隨機的私鑰得到公鑰。然後雙方交換各自的公鑰,這樣雙方都可以通過自己隨機的私鑰和對方的公鑰來生成一個同樣的共享密鑰(shared-secret)。後續的通訊使用這個共享密鑰作為對稱加密算法的密鑰。這樣來說。如果有一天一方的私鑰被泄露,也只會影響泄露之後的消息的安全性,對於之前的通訊是安全的(因為通訊的密鑰是隨機生成的,用完後就消失了)。
前向安全性
前向安全或前向保密FS(ForwardSecrecy),有時也被稱為完美前向安全PFS(PerfectForwardSecrecy),是密碼學中通訊協議的安全屬性,指的是長期使用的主密鑰泄漏不會導致過去的會話密鑰泄漏。前向安全能夠保護過去進行的通訊不受密碼或密鑰在未來暴露的威脅。如果系統具有前向安全性,就可以保證萬一密碼或密鑰在某個時刻不慎泄露,過去已經進行的通訊依然是安全,不會受到任何影響,即使系統遭到主動攻擊也是如此。
最後,需要說明一下,這篇文檔對RLPx協議進行了簡述,具體實現協議還是有很多細節需處理,深入請看以太坊源碼。
如果文中有問題或者不對的地方,可關注微信公眾號與我交流學習,歡迎指教!