以太坊RLPx傳輸協議

本文主要內容翻譯自:The RLPx Transport Protocol,其中添加了一些我的的理解,因爲密碼學水平有限,不正確之處望指正。另外原文可能已經更新,最新內容請直接閱讀原文。git

本文檔定義了RLPx傳輸協議,一種基於TCP的用於Ethereum節點間通訊的傳輸協議。該協議適用於任意內容的加密幀,但它一般用於承載devp2p應用程序協議。github

節點標識

全部加密操做都基於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_E\mid\mid k_M=KDF(S,32)\)導出加密和認證主密鑰,隨機生成一個初始向量\(iv\)。Alice向Bob發送加密消息\(R \mid\mid iv \mid\mid c \mid\mid d\),其中\(c=AES(k_E,iv,m)\)\(d=MAC(k_M,iv\mid\mid c)\)函數

Bob須要解密消息\(R \mid\mid iv \mid\mid c \mid\mid d\),爲此,須要導出共享密鑰\(S=P_x\),其中\((P_x,P_y)=k_B*R\),以及導出加密和認證密鑰\(k_E \mid\mid k_M=KDF(S,32)\)。Bob經過等式\(d==MAC(k_M,iv\mid\mid c)\)是否成立驗證消息後計算\(m=AES(k_E,iv\mid\mid 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
  1. 用臨時密鑰,必定程度上能夠保證前向安全性。後文中有前向安全性的描述。
  2. 握手過程最重要的是協商密鑰(對應上面的aes-secret、mac-secret)。
  3. 密鑰協商過程當中須要知道本節點和對方節點的(公鑰+臨時公鑰+隨機數)。這不是絕對的,不一樣的密鑰協商算法有不一樣的實現方式,但基本上都須要雙方交換一些數據,從而分別推到出相同的密鑰。

補充一點,整個協商密鑰的過程核心是ECDH密鑰協商,但ECDH協商的過程前提是要對方是認證過的,可信的,前面的ECIES加密,實際上至關於對消息接收方進行認證,由於只有擁有對應的私鑰才能解密消息。

建立加密鏈接主要流程以下:

  1. 發起端向接收端發起TCP鏈接,發送auth消息
  2. 接收端接受鏈接,解密、驗證auth消息(檢查recovery of signature == keccak256(ephemeral-pubk)
  3. 接收端生成auth-ack消息
  4. 接收端導出密鑰,發送第一個數據幀
  5. 發起端接收到auth-ack消息,導出密鑰
  6. 發起端發送第一個數據幀(代碼中對應Hello packet
  7. 接收端接收並驗證數據幀
  8. 發起端接收並驗證數據幀
  9. 若是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-macingress-mac實現消息認證;對頭部數據,是經過將加密輸出數據的頭部與相應的mac進行異或運算(參見header-mac)。這樣作是爲了確保對明文mac和密文執行統一操做。全部的mac都是以明文形式發送的。

填充字節用於防止緩存區飢餓,使得幀組件按指定區塊字節大小對齊。

已知的問題

  • RLPx握手被認爲是易破解的,由於aes-secretmac-secret被重複用於讀取和寫入
    。RLPx鏈接的兩端從相同的密鑰,nonce和IV生成兩個CTR流。若是攻擊者知道一個明文,他們就能夠用重用的密鑰流破解未知明文。
  • 幀編碼提供了用於多路複用的協議類型字段protocol-type,但devp2p未使用該字段。

RLPx傳輸協議的前向安全性

RLPx使用了(PerfectForwardSecrecy),簡單來講。連接的兩方都生成隨機的私鑰,經過隨機的私鑰獲得公鑰。而後雙方交換各自的公鑰,這樣雙方均可以經過本身隨機的私鑰和對方的公鑰來生成一個一樣的共享密鑰(shared-secret)。後續的通信使用這個共享密鑰做爲對稱加密算法的密鑰。這樣來講。若是有一天一方的私鑰被泄露,也只會影響泄露以後的消息的安全性,對於以前的通信是安全的(由於通信的密鑰是隨機生成的,用完後就消失了)。

前向安全性

前向安全或前向保密FS(ForwardSecrecy),有時也被稱爲完美前向安全PFS(PerfectForwardSecrecy),是密碼學中通信協議的安全屬性,指的是長期使用的主密鑰泄漏不會致使過去的會話密鑰泄漏。前向安全可以保護過去進行的通信不受密碼或密鑰在將來暴露的威脅。若是系統具備前向安全性,就能夠保證萬一密碼或密鑰在某個時刻不慎泄露,過去已經進行的通信依然是安全,不會受到任何影響,即便系統遭到主動攻擊也是如此。

最後,須要說明一下,這篇文檔對RLPx協議進行了簡述,具體實現協議仍是有不少細節需處理,深刻請看以太坊源碼。

若是文中有問題或者不對的地方,可關注微信公衆號與我交流學習,歡迎指教!

相關文章
相關標籤/搜索