標籤: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節點之間的加密鏈路二