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

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

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

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


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

查看所有标签

猜你喜欢:

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

Learning PHP 5

Learning PHP 5

David Sklar / O'Reilly / July, 2004 / $29.95

Learning PHP 5 is the ideal tutorial for graphic designers, bloggers, and other web crafters who want a thorough but non-intimidating way to understand the code that makes web sites dynamic. The book ......一起来看看 《Learning PHP 5》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具