美文网首页区块链教程
兄弟连区块链入门教程以太坊源码分析p2p-rlpx节点之间的加密

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

作者: ab6973df9221 | 来源:发表于2018-10-25 17:44 被阅读1次

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

        // 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*

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

更多区块链教程记得关注:http://t.cn/EZtFYV0

相关文章

网友评论

    本文标题:兄弟连区块链入门教程以太坊源码分析p2p-rlpx节点之间的加密

    本文链接:https://www.haomeiwen.com/subject/hzuctqtx.html