區塊鏈入門教程以太坊源碼分析p2p-rlpx節點之間的加密鏈路二

來源:互聯網
上載者:User

標籤:signature   方法   mes   []   contex   frame   xor   hash   adt   

// 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 xorSingle-frame packet:header || header-mac || frame || frame-macMulti-frame packet:header || header-mac || frame-0 ||[ header || header-mac || frame-n || ... || ]header || header-mac || frame-last || frame-macheader: frame-size || header-data || paddingframe-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**32padding: zero-fill to 16-byte boundaryheader-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digestframe: normal: rlp(packet-type) [|| rlp(packet-data)] || padding chunked-0: rlp(packet-type) || rlp(packet-data...) chunked-n: rlp(...packet-data) || paddingpadding: 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*

因為加密解密演算法我也不是很熟,所以這裡的分析還不是很徹底。 暫時只是分析了大致的流程。還有很多細節沒有確認。

區塊鏈入門教程以太坊源碼分析p2p-rlpx節點之間的加密鏈路二

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.