以太坊源码分析(47)p2p-rlpx节点之间的加密链路

RLPx协议在以太坊中用于节点间创建加密的TCP连接,确保通信安全。通过前向安全性,即使私钥泄露,历史通信依然安全。协议包括交换随机私钥以生成共享密钥,然后使用此密钥进行对称加密。迪菲-赫尔曼密钥交换在此过程中起关键作用,允许双方在没有预先信息的情况下创建密钥。doEncHandshake和doProtoHandshake方法分别处理密钥交换和协议协商,确保链接建立后的数据分帧和安全传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的链路协议。

链接联系的大致流程如下:

1. doEncHandshake() 通过这个方法来完成交换密钥,创建加密信道的流程。如果失败,那么链接关闭。
2. 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.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值