本文主要內容翻譯自:The RLPx Transport Protocol,其中添加了一些我的的理解,因爲密碼學水平有限,不正確之處望指正。另外原文可能已經更新,最新內容請直接閱讀原文。git
本文檔定義了RLPx傳輸協議,一種基於TCP的用於Ethereum節點間通訊的傳輸協議。該協議適用於任意內容的加密幀,但它一般用於承載devp2p應用程序協議。github
全部加密操做都基於secp256k1
橢圓曲線。每一個節點都須要維護一個在會話間保存和復原的靜態私鑰。建議私鑰只能手動重置,例如,經過刪除文件或數據庫條目。算法
ECIES(Elliptic Curve Integrated Encryption Scheme)是在RLPx握手中使用的非對稱加密方法。RLPx使用的密碼系統是:數據庫
secp256k1
橢圓曲線,生成元\(G\)NIST SP 800-56
級聯密鑰導出函數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
- 用臨時密鑰,必定程度上能夠保證前向安全性。後文中有前向安全性的描述。
- 握手過程最重要的是協商密鑰(對應上面的aes-secret、mac-secret)。
- 密鑰協商過程當中須要知道本節點和對方節點的(公鑰+臨時公鑰+隨機數)。這不是絕對的,不一樣的密鑰協商算法有不一樣的實現方式,但基本上都須要雙方交換一些數據,從而分別推到出相同的密鑰。
補充一點,整個協商密鑰的過程核心是ECDH密鑰協商,但ECDH協商的過程前提是要對方是認證過的,可信的,前面的ECIES加密,實際上至關於對消息接收方進行認證,由於只有擁有對應的私鑰才能解密消息。
建立加密鏈接主要流程以下:
auth
消息auth
消息(檢查recovery of signature == keccak256(ephemeral-pubk)
)auth-ack
消息auth-ack
消息,導出密鑰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都是以明文形式發送的。
填充字節用於防止緩存區飢餓,使得幀組件按指定區塊字節大小對齊。
aes-secret
和mac-secret
被重複用於讀取和寫入protocol-type
,但devp2p未使用該字段。RLPx使用了(PerfectForwardSecrecy),簡單來講。連接的兩方都生成隨機的私鑰,經過隨機的私鑰獲得公鑰。而後雙方交換各自的公鑰,這樣雙方均可以經過本身隨機的私鑰和對方的公鑰來生成一個一樣的共享密鑰(shared-secret)。後續的通信使用這個共享密鑰做爲對稱加密算法的密鑰。這樣來講。若是有一天一方的私鑰被泄露,也只會影響泄露以後的消息的安全性,對於以前的通信是安全的(由於通信的密鑰是隨機生成的,用完後就消失了)。
前向安全或前向保密FS(ForwardSecrecy),有時也被稱爲完美前向安全PFS(PerfectForwardSecrecy),是密碼學中通信協議的安全屬性,指的是長期使用的主密鑰泄漏不會致使過去的會話密鑰泄漏。前向安全可以保護過去進行的通信不受密碼或密鑰在將來暴露的威脅。若是系統具備前向安全性,就能夠保證萬一密碼或密鑰在某個時刻不慎泄露,過去已經進行的通信依然是安全,不會受到任何影響,即便系統遭到主動攻擊也是如此。
最後,須要說明一下,這篇文檔對RLPx協議進行了簡述,具體實現協議仍是有不少細節需處理,深刻請看以太坊源碼。
若是文中有問題或者不對的地方,可關注微信公衆號與我交流學習,歡迎指教!