ITPub博客

首页 > 区块链 > 区块链 > 区块链入门教程以太坊源码分析p2p-rlpx节点之间的加密链路二

区块链入门教程以太坊源码分析p2p-rlpx节点之间的加密链路二

原创 区块链 作者:兄弟连区块链入门教程 时间:2018-10-25 16:37:58 0 删除 编辑
         // Sign known message: static-shared-secret ^ nonce
        // 这个地方应该是直接使用了静态的共享秘密。 使用自己的私钥和对方的公钥生成的一个共享秘密。
        token, err = h.staticSharedSecret(prv)
        if err != nil {
            return nil, err
        }
        //这里我理解用共享秘密来加密这个initNonce。
        signed := xor(token, h.initNonce)
        // 使用随机的私钥来加密这个信息。
        signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
        if err != nil {
            return nil, err
        }
    
        msg := new(authMsgV4)
        copy(msg.Signature[:], signature)
        //这里把发起者的公钥告知对方。 这样对方使用自己的私钥和这个公钥可以生成静态的共享秘密。
        copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
        copy(msg.Nonce[:], h.initNonce)
        msg.Version = 4
        return msg, nil
    }
    // staticSharedSecret returns the static shared secret, the result
    // of key agreement between the local and remote static node key.
    func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
        return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
    }

sealEIP8方法,这个方法是一个组包方法,对msg进行rlp的编码。 填充一些数据。 然后使用对方的公钥把数据进行加密。 这意味着只有对方的私钥才能解密这段信息。

    func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {
        buf := new(bytes.Buffer)
        if err := rlp.Encode(buf, msg); err != nil {
            return nil, err
        }
        // pad with random amount of data. the amount needs to be at least 100 bytes to make
        // the message distinguishable from pre-EIP-8 handshakes.
        pad := padSpace[:mrand.Intn(len(padSpace)-100)+100]
        buf.Write(pad)
        prefix := make([]byte, 2)
        binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead))
    
        enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix)
        return append(prefix, enc...), err
    }

readHandshakeMsg这个方法会从两个地方调用。 一个是在initiatorEncHandshake。一个就是在receiverEncHandshake。 这个方法比较简单。 首先用一种格式尝试解码。如果不行就换另外一种。应该是一种兼容性的设置。 基本上就是使用自己的私钥进行解码然后调用rlp解码成结构体。 结构体的描述就是下面的authRespV4,里面最重要的就是对端的随机公钥。 双方通过自己的私钥和对端的随机公钥可以得到一样的共享秘密。 而这个共享秘密是第三方拿不到的。

   
    // RLPx v4 handshake response (defined in EIP-8).
    type authRespV4 struct {
        RandomPubkey [pubLen]byte
        Nonce [shaLen]byte
        Version uint
    
        // Ignore additional fields (forward-compatibility)
        Rest []rlp.RawValue `rlp:"tail"`
    }
    func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {
        buf := make([]byte, plainSize)
        if _, err := io.ReadFull(r, buf); err != nil {
            return buf, err
        }
        // Attempt decoding pre-EIP-8 "plain" format.
        key := ecies.ImportECDSA(prv)
        if dec, err := key.Decrypt(rand.Reader, buf, nil, nil); err == nil {
            msg.decodePlain(dec)
            return buf, nil
        }
        // Could be EIP-8 format, try that.
        prefix := buf[:2]
        size := binary.BigEndian.Uint16(prefix)
        if size < uint16(plainSize) {
            return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize)
        }
        buf = append(buf, make([]byte, size-uint16(plainSize)+2)...)
        if _, err := io.ReadFull(r, buf[plainSize:]); err != nil {
            return buf, err
        }
        dec, err := key.Decrypt(rand.Reader, buf[2:], nil, prefix)
        if err != nil {
            return buf, err
        }
        // Can't use rlp.DecodeBytes here because it rejects
        // trailing data (forward-compatibility).
        s := rlp.NewStream(bytes.NewReader(dec), 0)
        return buf, s.Decode(msg)
    }

handleAuthResp这个方法非常简单。

    func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) {
        h.respNonce = msg.Nonce[:]
        h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:])
        return err
    }

最后是secrets函数,这个函数是在handshake完成之后调用。它通过自己的随机私钥和对端的公钥来生成一个共享秘密,这个共享秘密是瞬时的(只在当前这个链接中存在)。所以当有一天私钥被破解。 之前的消息还是安全的。

    // secrets is called after the handshake is completed.
    // It extracts the connection secrets from the handshake values.
    func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
        ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
        if err != nil {
            return secrets{}, err
        }
    
        // derive base secrets from ephemeral key agreement
        sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
        aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
        // 实际上这个MAC保护了ecdheSecret这个共享秘密。respNonce和initNonce这三个值
        s := secrets{
            RemoteID: h.remoteID,
            AES: aesSecret,
            MAC: crypto.Keccak256(ecdheSecret, aesSecret),
        }
    
        // setup sha3 instances for the MACs
        mac1 := sha3.NewKeccak256()
        mac1.Write(xor(s.MAC, h.respNonce))
        mac1.Write(auth)
        mac2 := sha3.NewKeccak256()
        mac2.Write(xor(s.MAC, h.initNonce))
        mac2.Write(authResp)
        //收到的每个包都会检查其MAC值是否满足计算的结果。如果不满足说明有问题。
        if h.initiator {
            s.EgressMAC, s.IngressMAC = mac1, mac2
        } else {
            s.EgressMAC, s.IngressMAC = mac2, mac1
        }
    
        return s, nil
    }

receiverEncHandshake函数和initiatorEncHandshake的内容大致相同。 但是顺序有些不一样。

   
    // receiverEncHandshake negotiates a session token on conn.
    // it should be called on the listening side of the connection.
    //
    // prv is the local client's private key.
    // token is the token from a previous session with this node.
    func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {
        authMsg := new(authMsgV4)
        authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
        if err != nil {
            return s, err
        }
        h := new(encHandshake)
        if err := h.handleAuthMsg(authMsg, prv); err != nil {
            return s, err
        }
    
        authRespMsg, err := h.makeAuthResp()
        if err != nil {
            return s, err
        }
        var authRespPacket []byte
        if authMsg.gotPlain {
            authRespPacket, err = authRespMsg.sealPlain(h)
        } else {
            authRespPacket, err = sealEIP8(authRespMsg, h)
        }
        if err != nil {
            return s, err
        }
        if _, err = conn.Write(authRespPacket); err != nil {
            return s, err
        }
        return h.secrets(authPacket, authRespPacket)
    }

doProtocolHandshake

这个方法比较简单, 加密信道已经创建完毕。 我们看到这里只是约定了是否使用Snappy加密然后就退出了。

    // doEncHandshake runs the protocol handshake using authenticated
    // messages. the protocol handshake is the first authenticated message
    // and also verifies whether the encryption handshake 'worked' and the
    // remote side actually provided the right public key.
    func (t *rlpx) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
        // Writing our handshake happens concurrently, we prefer
        // returning the handshake read error. If the remote side
        // disconnects us early with a valid reason, we should return it
        // as the error so it can be tracked elsewhere.
        werr := make(chan error, 1)
        go func() { werr <- Send(t.rw, handshakeMsg, our) }()
        if their, err = readProtocolHandshake(t.rw, our); err != nil {
            <-werr // make sure the write terminates too
            return nil, err
        }
        if err := <-werr; err != nil {
            return nil, fmt.Errorf("write error: %v", err)
        }
        // If the protocol version supports Snappy encoding, upgrade immediately
        t.rw.snappy = their.Version >= snappyProtocolVersion
    
        return their, nil
    }

rlpxFrameRW 数据分帧

数据分帧主要通过rlpxFrameRW类来完成的。

    // rlpxFrameRW implements a simplified version of RLPx framing.
    // chunked messages are not supported and all headers are equal to
    // zeroHeader.
    //
    // rlpxFrameRW is not safe for concurrent use from multiple goroutines.
    type rlpxFrameRW struct {
        conn io.ReadWriter
        enc cipher.Stream
        dec cipher.Stream
    
        macCipher cipher.Block
        egressMAC hash.Hash
        ingressMAC hash.Hash
    
        snappy bool
    }

我们在完成两次握手之后。调用newRLPXFrameRW方法创建了这个对象。

    t.rw = newRLPXFrameRW(t.fd, sec)

然后提供ReadMsg和WriteMsg方法。这两个方法直接调用了rlpxFrameRW的ReadMsg和WriteMsg

    func (t *rlpx) ReadMsg() (Msg, error) {
        t.rmu.Lock()
        defer t.rmu.Unlock()
        t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout))
        return t.rw.ReadMsg()
    }
    func (t *rlpx) WriteMsg(msg Msg) error {
        t.wmu.Lock()
        defer t.wmu.Unlock()
        t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
        return t.rw.WriteMsg(msg)
    }
WriteMsg
    func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
        ptype, _ := rlp.EncodeToBytes(msg.Code)
    
        // if snappy is enabled, compress message now
        if rw.snappy {
            if msg.Size > maxUint24 {
                return errPlainMessageTooLarge
            }
            payload, _ := ioutil.ReadAll(msg.Payload)
            payload = snappy.Encode(nil, payload)
    
            msg.Payload = bytes.NewReader(payload)
            msg.Size = uint32(len(payload))
        }
        // write header
        headbuf := make([]byte, 32)
        fsize := uint32(len(ptype)) + msg.Size
        if fsize > maxUint24 {
            return errors.New("message size overflows uint24")
        }
        putInt24(fsize, headbuf) // TODO: check overflow
        copy(headbuf[3:], zeroHeader)
        rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted
    
        // write header MAC
        copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))
        if _, err := rw.conn.Write(headbuf); err != nil {
            return err
        }
    
        // write encrypted frame, updating the egress MAC hash with
        // the data written to conn.
        tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)}
        if _, err := tee.Write(ptype); err != nil {
            return err
        }
        if _, err := io.Copy(tee, msg.Payload); err != nil {
            return err
        }
        if padding := fsize % 16; padding > 0 {
            if _, err := tee.Write(zero16[:16-padding]); err != nil {
                return err
            }
        }
    
        // write frame MAC. egress MAC hash is up to date because
        // frame content was written to it as well.
        fmacseed := rw.egressMAC.Sum(nil)
        mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)
        _, err := rw.conn.Write(mac)
        return err
    }
ReadMsg
    func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {
        // read the header
        headbuf := make([]byte, 32)
        if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
            return msg, err
        }
        // verify header mac
        shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16])
        if !hmac.Equal(shouldMAC, headbuf[16:]) {
            return msg, errors.New("bad header MAC")
        }
        rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted
        fsize := readInt24(headbuf)
        // ignore protocol type for now
    
        // read the frame content
        var rsize = fsize // frame size rounded up to 16 byte boundary
        if padding := fsize % 16; padding > 0 {
            rsize += 16 - padding
        }
        framebuf := make([]byte, rsize)
        if _, err := io.ReadFull(rw.conn, framebuf); err != nil {
            return msg, err
        }
    
        // read and validate frame MAC. we can re-use headbuf for that.
        rw.ingressMAC.Write(framebuf)
        fmacseed := rw.ingressMAC.Sum(nil)
        if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil {
            return msg, err
        }
        shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed)
        if !hmac.Equal(shouldMAC, headbuf[:16]) {
            return msg, errors.New("bad frame MAC")
        }
    
        // decrypt frame content
        rw.dec.XORKeyStream(framebuf, framebuf)
    
        // decode message code
        content := bytes.NewReader(framebuf[:fsize])
        if err := rlp.Decode(content, &msg.Code); err != nil {
            return msg, err
        }
        msg.Size = uint32(content.Len())
        msg.Payload = content
    
        // if snappy is enabled, verify and decompress message
        if rw.snappy {
            payload, err := ioutil.ReadAll(msg.Payload)
            if err != nil {
                return msg, err
            }
            size, err := snappy.DecodedLen(payload)
            if err != nil {
                return msg, err
            }
            if size > int(maxUint24) {
                return msg, errPlainMessageTooLarge
            }
            payload, err = snappy.Decode(nil, payload)
            if err != nil {
                return msg, err
            }
            msg.Size, msg.Payload = uint32(size), bytes.NewReader(payload)
        }
        return msg, nil
    }

帧结构

     normal = not chunked
     chunked-0 = First frame of a multi-frame packet
     chunked-n = Subsequent frames for multi-frame packet
     || is concatenate
     ^ is xor
    
    Single-frame packet:
    header || header-mac || frame || frame-mac
    
    Multi-frame packet:
    header || header-mac || frame-0 ||
    [ header || header-mac || frame-n || ... || ]
    header || header-mac || frame-last || frame-mac
    
    header: frame-size || header-data || padding
    frame-size: 3-byte integer size of frame, big endian encoded (excludes padding)
    header-data:
     normal: rlp.list(protocol-type[, context-id])
     chunked-0: rlp.list(protocol-type, context-id, total-packet-size)
     chunked-n: rlp.list(protocol-type, context-id)
     values:
     protocol-type: < 2**16
     context-id: < 2**16 (optional for normal frames)
     total-packet-size: < 2**32
    padding: zero-fill to 16-byte boundary
    
    header-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digest
    
    frame:
     normal: rlp(packet-type) [|| rlp(packet-data)] || padding
     chunked-0: rlp(packet-type) || rlp(packet-data...)
     chunked-n: rlp(...packet-data) || padding
    padding: zero-fill to 16-byte boundary (only necessary for last frame)
    
    frame-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ right128(egress-mac.update(frame-ciphertext).digest))
    
    egress-mac: h256, continuously updated with egress-bytes*
    ingress-mac: h256, continuously updated with ingress-bytes*

因为加密解密算法我也不是很熟,所以这里的分析还不是很彻底。 暂时只是分析了大致的流程。还有很多细节没有确认。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31557831/viewspace-2217539/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-10-11

  • 博文量
    79
  • 访问量
    59477