兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路Word文档下载推荐.docx
《兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《兄弟连Go语言+区块链技术培训以太坊源码分析47p2prlpx节点之间的加密链路Word文档下载推荐.docx(18页珍藏版)》请在冰豆网上搜索。
墨克(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)
returns,err
authPacket,err:
=sealEIP8(authMsg,h)
if_,err=conn.Write(authPacket);
err!
authRespMsg:
=new(authRespV4)
authRespPacket,err:
=readHandshakeMsg(authRespMsg,encAuthRespLen,prv,conn)
iferr:
=h.handleAuthResp(authRespMsg);
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()
returnnil,fmt.Errorf("
badremoteID:
%v"
err)
h.remotePub=ecies.ImportECDSAPublic(rpub)
//Generaterandominitiatornonce.
//生成一个随机的初始值,是为了避免重放攻击么?
还是为了避免通过多次连接猜测密钥?
h.initNonce=make([]byte,shaLen)
if_,err:
=rand.Read(h.initNonce);
returnnil,err
//GeneraterandomkeypairtoforECDH.
//生成一个随机的私钥
h.randomPrivKey,err=ecies.GenerateKey(rand.Reader,crypto.S256(),nil)
//Signknownmessage:
static-shared-secret^nonce
//这个地方应该是直接使用了静态的共享秘密。
使用自己的私钥和对方的公钥生成的一个共享秘密。
token,err=h.staticSharedSecret(prv)
//这里我理解用共享秘密来加密这个initNonce。
signed:
=xor(token,h.initNonce)
//使用随机的私钥来加密这个信息。
signature,err:
=crypto.Sign(signed,h.randomPrivKey.ExportECDSA())
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)
=rlp.Encode(buf,msg);
//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){
=make([]byte,plainSize)
=io.ReadFull(r,buf);
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.
=buf[:
2]
size:
=binary.BigEndian.Uint16(prefix)
ifsize<
uint16(plainSize){
returnbuf,fmt.Errorf("
sizeunderflow,needatleast%dbytes"
plainSize)
buf=append(buf,make([]byte,size-uint16(plainSize)+2)...)
=io.ReadFull(r,buf[plainSize:
]);
dec,err:
=key.Decrypt(rand.Reader,buf[2:
],nil,prefix)
//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)
returnsecrets{},err
//derivebasesecretsfromephemeralkeyagreement
sharedSecret:
=crypto.Keccak256(ecdheSecret,crypto.Keccak256(h.respNonce,h.initNonce))
aesSecret:
=crypto.Keccak256(ecdheSecret,sharedSecret)
//实际上这个MAC保护了ecdheSecret这个共享秘密。
respNonce和initNonce这三个值
=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:
mac2.Write(xor(s.MAC,h.initNonce))
mac2.Write(authResp)
//收到的每个包都会检查其MAC值是否满足计算的结果。
如果不满足说明有问题。
ifh.initiator{
s.EgressMAC,s.IngressMAC=mac1,mac2
s.EgressMAC,s.IngressMAC=mac2,mac1
returns,nil
receiverEncHandshake函数和initiatorEncHandshake的内容大致相同。
但是顺序有些不一样。
//receiverEncHandshakenegoti