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

栏目: 编程工具 · 发布时间: 6年前

内容简介:兄弟连区块链入门教程以太坊源码分析p2p-rlpx节点之间的加密链路二sealEIP8方法,这个方法是一个组包方法,对msg进行rlp的编码。 填充一些数据。 然后使用对方的公钥把数据进行加密。 这意味着只有对方的私钥才能解密这段信息。readHandshakeMsg这个方法会从两个地方调用。 一个是在initiatorEncHandshake。一个就是在receiverEncHandshake。 这个方法比较简单。 首先用一种格式尝试解码。如果不行就换另外一种。应该是一种兼容性的设置。 基本上就是使用自

兄弟连区块链入门教程以太坊源码分析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*

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

REST实战

REST实战

Jim Webber、Savas Parastatidis、Ian Robinson / 李锟、俞黎敏、马钧、崔毅 / 东南大学出版社 / 2011-10 / 78.00元

为何典型的企业项目无法像你为web所开发的项目那样运行得如此平滑?对于建造分布式和企业级的应用来说,rest架构风格真的提供了一个可行的替代选择吗? 在这本富有洞察力的书中,三位soa专家对于rest进行了讲求实际的解释,并且通过将web的指导原理应用到普通的企业计算问题中,向你展示了如何开发简单的、优雅的分布式超媒体系统。你将会学习到很多技术,并且随着一家典型的公司从最初的小企业逐渐成长为......一起来看看 《REST实战》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具