ECIES(Elliptic Curve Integrated Encryption Scheme,橢圓曲線綜合加密方案) 作為非對稱秘鑰 用於RLPx協議握手。在RLPx協議中用到EXIES的以下幾個點:
RLPx 協議建立連線
通訊雙方進行加密握手,生成共享秘密建立加密通道,再進行協議握手。後續業務資料在這個加密通道中傳輸。
發起者(initator ) 為主動開啟TCP連線的一個節點,接受者(Recipient)為接受連線的節點。
節點透過底層的節點發現機制獲取對方節點的 ip,tcp地址資訊。發起者開啟tcp連線。
E 為EXIES 非對稱加密函式,Rlpx 加密握手協議如下:
authPacket -> E(remote-pubk, S(ephemeral-privk, static-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0)
authResponsePacket- -> E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0)
static-shared-secret = ecdh.agree(privkey, remote-pubk)
加密握手
發起者的加密握手流程如下:
1.生成一個隨機數init-nonce
2.透過ecies生成隨機秘鑰對 ,隨機私鑰ephemeral-privk 與隨機公鑰ephemeral-pubk
3.用自己的私鑰privk和對方的公鑰remote_pubk 生成靜態共享秘密static-shared-secrets
4.將生成的共享秘密static-shared-secrets與隨機數init-nonce進行異或運算,得到一個雜湊值
5.使用自己的隨機私鑰ephemeral-privk 對該雜湊值進行ECDSA簽名計算,得到簽名sig
6.將簽名sig、自己的公鑰pubk 、初始nonce 作為認證資訊authMsg。
7.對authMsg進行編碼,然後再用對方的公鑰remote_pubk進行ecies加密,得到認證資料包authPacket,將資料包透過傳送給對方節點
8.等待讀取對方節點的響應
9.收到對方響應之後,讀取資料,使用自己的私鑰privk 進行解密,再進行解碼,得到認證響應authRespMsg
10.讀取響應nonce, 和對方的隨機公鑰remote-ephemeral-pubk。
接受者加密握手流程:
1.接收方讀取對方傳送的加密握手資料包。
2.先透過自己的私鑰privk解密資料包,然後進行解碼,得到認證資料authMsg。
3.獲取對方的remote_pubk, 和nonce。
4.生成隨機ECDH 秘鑰對, 私鑰ephemeral-privk 與公鑰ephemeral-pubk。
5.透過自己的私鑰privk 和對方的公鑰remote-pubk生成協商的靜態共享秘密static-shared-secrets
6.用nonce對靜態共享秘密進行異或運算得到 簽名的訊息signedMsg
7.用signedMsg 和authMsg中的簽名資訊,恢復出對方的隨機公鑰remote-ephemeral-pubk
8.生成隨機數responseNonce, 與隨機公鑰ephemeral-pubk 作為認證響應authRespMsg
9.對authRespMsg進行編碼, 然後用對方的公鑰remote-pubk進行加密生成認證響應資料authresponsePacket
10.將authResponsePacket 傳送給對方。
計算共享秘密(shared secret)
發起者和接受者在握手完成之後,透過認證訊息authPacket 和認證響應訊息authRespPacket 計算協商的連線秘密 。 該連線秘密只有在當前連線中有效,所以當一方的私鑰被洩露之後,之前的通訊訊息還是安全的。
計算共享秘密步驟如下:
1. 用當前連線的隨機私鑰ephemeral-privk和對端隨機公鑰emote-ephemeral-pubk計算一個個ECDH共享秘密:
2. 計算共享秘密:
3.計算AES秘密:
4. 計算訊息認證碼秘密
5. 對發起方(initiator),計算出口連線訊息認證碼
6.對發起方(initiator), 計算入口連線訊息認證碼
7. 對接收方(Recipient), 計算出口連線訊息認證碼
8. 對接收方(Recipient),計算入口連線訊息認證碼
9. 將對方公鑰remote-pubk, aes-secret, mac-secre,egress-mac, ingress-mac 作為當前連線的協商秘密(connection secrets) 用於資料分幀。
協議握手
協議握手傳送簡單的握手資訊,檢查對方的響應,判斷加密握手是否起作用,同時判斷對方是否支援snappy壓縮。握手成功則建立連線完成。
資料分幀
加密握手成功之後,在此連線上傳送的所有業務資訊,都透過連線協商秘密(connection secrets) 按一定格式進行資料分幀。
分幀格式:
frame = header || header-mac || frame-data || frame-mac
header = frame-size || header-data || padding
對於要傳送的資料,按如下步驟寫入tcp連線:
1. 如果啟用了snappy,則用snappy壓縮訊息內容
2. 將訊息大小和型別等寫入訊息頭header
3. 計算訊息頭認證碼header-mac
4. 對訊息內容加密,寫入加密後的的幀資料 frame-data
5. 計算幀訊息認證碼,並寫入
對於從tcp層接受到到資料,按如下步驟讀取:
1. 讀取32個位元組的訊息頭header
2. 校驗header-mac 是否正確
3. 讀取訊息型別和訊息大小
4. 校驗frame-mac是否正確
5. 讀取訊息內容並使用當前連線的秘密(connection secrets)AES解密
6. 如果使用了snappy壓縮,則用snappy解壓獲取訊息內容
7. 讀取完畢,將訊息內容交給上層協議處理
透過密文持續更新egress-mac 與ingress-mac 以及header-mac得到訊息認證。
Reference : https://github.com/ethereum/go-ethereum/tree/master/p2p
注: || 為串接 ,^為異或運算。