兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路.docx
- 文档编号:4037717
- 上传时间:2022-11-27
- 格式:DOCX
- 页数:18
- 大小:23.04KB
兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路.docx
《兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路.docx》由会员分享,可在线阅读,更多相关《兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路.docx(18页珍藏版)》请在冰豆网上搜索。
兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路
兄弟连Go语言+区块链技术培训以太坊源码分析(47)p2p-rlpx节点之间的加密链路
RLPxEncryption(RLPx加密)
之前介绍的discover节点发现协议,因为承载的数据不是很重要,基本是明文传输的。
每一个节点会开启两个同样的端口,一个是UDP端口,用来节点发现,一个是TCP端口,用来承载业务数据。
UDP的端口和TCP的端口的端口号是同样的。
这样只要通过UDP发现了端口,就等于可以用TCP来连接到对应的端口。
RLPx协议就定义了TCP链接的加密过程。
RLPx使用了(PerfectForwardSecrecy),简单来说。
链接的两方生成生成随机的私钥,通过随机的私钥得到公钥。
然后双方交换各自的公钥,这样双方都可以通过自己随机的私钥和对方的公钥来生成一个同样的共享密钥(shared-secret)。
后续的通讯使用这个共享密钥作为对称加密算法的密钥。
这样来说。
如果有一天一方的私钥被泄露,也只会影响泄露之后的消息的安全性,对于之前的通讯是安全的(因为通讯的密钥是随机生成的,用完后就消失了)。
##前向安全性(引用自维基百科)
前向安全或前向保密(英语:
ForwardSecrecy,缩写:
FS),有时也被称为完美前向安全[1](英语:
PerfectForwardSecrecy,缩写:
PFS),是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。
[2]前向安全能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。
[3]如果系统具有前向安全性,就可以保证万一密码或密钥在某个时刻不慎泄露,过去已经进行的通讯依然是安全,不会受到任何影响,即使系统遭到主动攻击也是如此。
###迪菲-赫尔曼密钥交换
迪菲-赫尔曼密钥交换(英语:
Diffie–Hellmankeyexchange,缩写为D-H)是一种安全协议。
它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道创建起一个密钥。
这个密钥可以在后续的通讯中作为对称密钥来加密通讯内容。
公钥交换的概念最早由瑞夫·墨克(RalphC.Merkle)提出,而这个密钥交换方法,由惠特菲尔德·迪菲(BaileyWhitfieldDiffie)和马丁·赫尔曼(MartinEdwardHellman)在1976年首次发表。
马丁·赫尔曼曾主张这个密钥交换方法,应被称为迪菲-赫尔曼-墨克密钥交换(英语:
Diffie–Hellman–Merklekeyexchange)。
- 迪菲-赫尔曼密钥交换的同义词包括:
- 迪菲-赫尔曼密钥协商
- 迪菲-赫尔曼密钥创建
- 指数密钥交换
- 迪菲-赫尔曼协议
虽然迪菲-赫尔曼密钥交换本身是一个匿名(无认证)的密钥交换协议,它却是很多认证协议的基础,并且被用来提供传输层安全协议的短暂模式中的完备的前向安全性。
####描述
迪菲-赫尔曼通过公共信道交换一个信息,就可以创建一个可以用于在公共信道上安全通信的共享秘密(sharedsecret)。
##p2p/rlpx.go源码解读
这个文件实现了RLPx的链路协议。
链接联系的大致流程如下:
1. doEncHandshake()通过这个方法来完成交换密钥,创建加密信道的流程。
如果失败,那么链接关闭。
2. doProtoHandshake()这个方法来进行协议特性之间的协商,比如双方的协议版本,是否支持Snappy加密方式等操作。
链接经过这两次处理之后,就算建立起来了。
因为TCP是流式的协议。
所有RLPx协议定义了分帧的方式。
所有的数据都可以理解为一个接一个的rlpxFrame。
rlpx的读写都是通过rlpxFrameRW对象来进行处理。
###doEncHandshake
链接的发起者被称为initiator。
链接的被动接受者被成为receiver。
这两种模式下处理的流程是不同的。
完成握手后。
生成了一个sec.可以理解为拿到了对称加密的密钥。
然后创建了一个newRLPXFrameRW帧读写器。
完成加密信道的创建过程。
func(t*rlpx)doEncHandshake(prv*ecdsa.PrivateKey,dial*discover.Node)(discover.NodeID,error){
var(
secsecrets
errerror
)
ifdial==nil{
sec,err=receiverEncHandshake(t.fd,prv,nil)
}else{
sec,err=initiatorEncHandshake(t.fd,prv,dial.ID,nil)
}
iferr!
=nil{
returndiscover.NodeID{},err
}
t.wmu.Lock()
t.rw=newRLPXFrameRW(t.fd,sec)
t.wmu.Unlock()
returnsec.RemoteID,nil
}
initiatorEncHandshake首先看看链接的发起者的操作。
首先通过makeAuthMsg创建了authMsg。
然后通过网络发送给对端。
然后通过readHandshakeMsg读取对端的回应。
最后调用secrets创建了共享秘密。
//initiatorEncHandshakenegotiatesasessiontokenonconn.
//itshouldbecalledonthedialingsideoftheconnection.
//
//prvisthelocalclient'sprivatekey.
funcinitiatorEncHandshake(connio.ReadWriter,prv*ecdsa.PrivateKey,remoteIDdiscover.NodeID,token[]byte)(ssecrets,errerror){
h:
=&encHandshake{initiator:
true,remoteID:
remoteID}
authMsg,err:
=h.makeAuthMsg(prv,token)
iferr!
=nil{
returns,err
}
authPacket,err:
=sealEIP8(authMsg,h)
iferr!
=nil{
returns,err
}
if_,err=conn.Write(authPacket);err!
=nil{
returns,err
}
authRespMsg:
=new(authRespV4)
authRespPacket,err:
=readHandshakeMsg(authRespMsg,encAuthRespLen,prv,conn)
iferr!
=nil{
returns,err
}
iferr:
=h.handleAuthResp(authRespMsg);err!
=nil{
returns,err
}
returnh.secrets(authPacket,authRespPacket)
}
makeAuthMsg。
这个方法创建了initiator的handshakemessage。
首先对端的公钥可以通过对端的ID来获取。
所以对端的公钥对于发起连接的人来说是知道的。
但是对于被连接的人来说,对端的公钥应该是不知道的。
//makeAuthMsgcreatestheinitiatorhandshakemessage.
func(h*encHandshake)makeAuthMsg(prv*ecdsa.PrivateKey,token[]byte)(*authMsgV4,error){
rpub,err:
=h.remoteID.Pubkey()
iferr!
=nil{
returnnil,fmt.Errorf("badremoteID:
%v",err)
}
h.remotePub=ecies.ImportECDSAPublic(rpub)
//Generaterandominitiatornonce.
//生成一个随机的初始值,是为了避免重放攻击么?
还是为了避免通过多次连接猜测密钥?
h.initNonce=make([]byte,shaLen)
if_,err:
=rand.Read(h.initNonce);err!
=nil{
returnnil,err
}
//GeneraterandomkeypairtoforECDH.
//生成一个随机的私钥
h.randomPrivKey,err=ecies.GenerateKey(rand.Reader,crypto.S256(),nil)
iferr!
=nil{
returnnil,err
}
//Signknownmessage:
static-shared-secret^nonce
//这个地方应该是直接使用了静态的共享秘密。
使用自己的私钥和对方的公钥生成的一个共享秘密。
token,err=h.staticSharedSecret(prv)
iferr!
=nil{
returnnil,err
}
//这里我理解用共享秘密来加密这个initNonce。
signed:
=xor(token,h.initNonce)
//使用随机的私钥来加密这个信息。
signature,err:
=crypto.Sign(signed,h.randomPrivKey.ExportECDSA())
iferr!
=nil{
returnnil,err
}
msg:
=new(authMsgV4)
copy(msg.Signature[:
],signature)
//这里把发起者的公钥告知对方。
这样对方使用自己的私钥和这个公钥可以生成静态的共享秘密。
copy(msg.InitiatorPubkey[:
],crypto.FromECDSAPub(&prv.PublicKey)[1:
])
copy(msg.Nonce[:
],h.initNonce)
msg.Version=4
returnmsg,nil
}
//staticSharedSecretreturnsthestaticsharedsecret,theresult
//ofkeyagreementbetweenthelocalandremotestaticnodekey.
func(h*encHandshake)staticSharedSecret(prv*ecdsa.PrivateKey)([]byte,error){
returnecies.ImportECDSA(prv).GenerateShared(h.remotePub,sskLen,sskLen)
}
sealEIP8方法,这个方法是一个组包方法,对msg进行rlp的编码。
填充一些数据。
然后使用对方的公钥把数据进行加密。
这意味着只有对方的私钥才能解密这段信息。
funcsealEIP8(msginterface{},h*encHandshake)([]byte,error){
buf:
=new(bytes.Buffer)
iferr:
=rlp.Encode(buf,msg);err!
=nil{
returnnil,err
}
//padwithrandomamountofdata.theamountneedstobeatleast100bytestomake
//themessagedistinguishablefrompre-EIP-8handshakes.
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)
returnappend(prefix,enc...),err
}
readHandshakeMsg这个方法会从两个地方调用。
一个是在initiatorEncHandshake。
一个就是在receiverEncHandshake。
这个方法比较简单。
首先用一种格式尝试解码。
如果不行就换另外一种。
应该是一种兼容性的设置。
基本上就是使用自己的私钥进行解码然后调用rlp解码成结构体。
结构体的描述就是下面的authRespV4,里面最重要的就是对端的随机公钥。
双方通过自己的私钥和对端的随机公钥可以得到一样的共享秘密。
而这个共享秘密是第三方拿不到的。
//RLPxv4handshakeresponse(definedinEIP-8).
typeauthRespV4struct{
RandomPubkey[pubLen]byte
Nonce[shaLen]byte
Versionuint
//Ignoreadditionalfields(forward-compatibility)
Rest[]rlp.RawValue`rlp:
"tail"`
}
funcreadHandshakeMsg(msgplainDecoder,plainSizeint,prv*ecdsa.PrivateKey,rio.Reader)([]byte,error){
buf:
=make([]byte,plainSize)
if_,err:
=io.ReadFull(r,buf);err!
=nil{
returnbuf,err
}
//Attemptdecodingpre-EIP-8"plain"format.
key:
=ecies.ImportECDSA(prv)
ifdec,err:
=key.Decrypt(rand.Reader,buf,nil,nil);err==nil{
msg.decodePlain(dec)
returnbuf,nil
}
//CouldbeEIP-8format,trythat.
prefix:
=buf[:
2]
size:
=binary.BigEndian.Uint16(prefix)
ifsize returnbuf,fmt.Errorf("sizeunderflow,needatleast%dbytes",plainSize) } buf=append(buf,make([]byte,size-uint16(plainSize)+2)...) if_,err: =io.ReadFull(r,buf[plainSize: ]);err! =nil{ returnbuf,err } dec,err: =key.Decrypt(rand.Reader,buf[2: ],nil,prefix) iferr! =nil{ returnbuf,err } //Can'tuserlp.DecodeBytesherebecauseitrejects //trailingdata(forward-compatibility). s: =rlp.NewStream(bytes.NewReader(dec),0) returnbuf,s.Decode(msg) } handleAuthResp这个方法非常简单。 func(h*encHandshake)handleAuthResp(msg*authRespV4)(errerror){ h.respNonce=msg.Nonce[: ] h.remoteRandomPub,err=importPublicKey(msg.RandomPubkey[: ]) returnerr } 最后是secrets函数,这个函数是在handshake完成之后调用。 它通过自己的随机私钥和对端的公钥来生成一个共享秘密,这个共享秘密是瞬时的(只在当前这个链接中存在)。 所以当有一天私钥被破解。 之前的消息还是安全的。 //secretsiscalledafterthehandshakeiscompleted. //Itextractstheconnectionsecretsfromthehandshakevalues. func(h*encHandshake)secrets(auth,authResp[]byte)(secrets,error){ ecdheSecret,err: =h.randomPrivKey.GenerateShared(h.remoteRandomPub,sskLen,sskLen) iferr! =nil{ returnsecrets{},err } //derivebasesecretsfromephemeralkeyagreement 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), } //setupsha3instancesfortheMACs 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值是否满足计算的结果。 如果不满足说明有问题。 ifh.initiator{ s.EgressMAC,s.IngressMAC=mac1,mac2 }else{ s.EgressMAC,s.IngressMAC=mac2,mac1 } returns,nil } receiverEncHandshake函数和initiatorEncHandshake的内容大致相同。 但是顺序有些不一样。 //receiverEncHandshakenegoti
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 兄弟连 Go 语言 区块 技术培训 以太 源码 分析 47 p2prlpx 节点 之间 加密
![提示](https://static.bdocx.com/images/bang_tan.gif)