本文主要内容翻译自:The RLPx Transport Protocol,其中添加了一些个人的理解,由于密码学水平有限,不正确之处望指正。另外原文可能已经更新,最新内容请直接阅读原文。

本文档定义了RLPx传输协议,一种基于TCP的用于Ethereum节点间通信的传输协议。该协议适用于任意内容的加密帧,但它通常用于承载devp2p应用程序协议。

节点标识

所有加密操作都基于secp256k1椭圆曲线。每个节点都需要维护一个在会话间保存和复原的静态私钥。建议私钥只能手动重置,例如,通过删除文件或数据库条目。

ECIES加密

ECIES(Elliptic Curve Integrated Encryption Scheme)是在RLPx握手中使用的非对称加密方法。RLPx使用的密码系统是:

  • secp256k1椭圆曲线,生成元\(G\)
  • \(KDF(k,len)\):NIST SP 800-56级联密钥导出函数
  • \(MAC(k,m)\):HMAC使用SHA-256哈希函数
  • \(AES(k,iv,m)\):AES-256加密算法CTR模式

这里原文是AES-128,但是Ethereum源代码中是AES-256,所以本文更改为AES-256

Alice想要发送可以被Bob的静态私钥\(k_B\)解密的加密消息。Alice知道Bob的静态公钥\(K_B\)。

为了加密消息\(m\),Alice生成了一个随机数\(r\),相应的生成了椭圆曲线公钥\(R=r*G\)并计算出共享密钥\(S=P_x\),\((P_x,P_y)=r*K_B\)。接着,\(k_E\mid\mid k_M=KDF(S,32)\)导出加密和认证主密钥,随机生成一个初始向量\(iv\)。Alice向Bob发送加密消息\(R \mid\mid iv \mid\mid c \mid\mid d\),其中\(c=AES(k_E,iv,m)\),\(d=MAC(k_M,iv\mid\mid c)\)。

Bob需要解密消息\(R \mid\mid iv \mid\mid c \mid\mid d\),为此,需要导出共享密钥\(S=P_x\),其中\((P_x,P_y)=k_B*R\),以及导出加密和认证密钥\(k_E \mid\mid k_M=KDF(S,32)\)。Bob通过等式\(d==MAC(k_M,iv\mid\mid c)\)是否成立验证消息后计算\(m=AES(k_E,iv\mid\mid c)\)获取明文消息。

握手

“握手”过程构建了会话阶段中使用的主密钥。握手是在发起端(发起TCP连接请求的节点)和接收端(接受连接的节点)之间完成。

握手协议:

E是上面定义的ECIES非对称加密函数。

补充说明: E代表加密;S代表签名;H代表Hash运算

auth -> E(remote-pubk, S(ephemeral-privk, static-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0)   # 由握手发起方发送,向对方发送密钥协商需要的本节点(公钥+临时公钥+随机数)
auth-ack -> E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) # 接收方回应auth消息,向对方发送密钥协商需要的本节点(临时公钥+随机数),本节点公钥对方已经知道,所以这里不需要发送了。 static-shared-secret = ecdh.agree(privkey, remote-pubk)

握手后值的计算(步骤如下):

ephemeral-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
shared-secret = keccak256(ephemeral-shared-secret || keccak256(nonce || initiator-nonce))
aes-secret = keccak256(ephemeral-shared-secret || shared-secret)
# destroy shared-secret
mac-secret = keccak256(ephemeral-shared-secret || aes-secret)
# destroy ephemeral-shared-secret Initiator:
egress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-sent-init)
# destroy nonce
ingress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-recvd-ack)
# destroy remote-nonce Recipient:
egress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-sent-ack)
# destroy nonce
ingress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-recvd-init)
# destroy remote-nonce
  1. 用临时密钥,一定程度上可以保证前向安全性。后文中有前向安全性的描述。
  2. 握手过程最重要的是协商密钥(对应上面的aes-secret、mac-secret)。
  3. 密钥协商过程中需要知道本节点和对方节点的(公钥+临时公钥+随机数)。这不是绝对的,不同的密钥协商算法有不同的实现方式,但基本上都需要双方交换一些数据,从而分别推到出相同的密钥。

补充一点,整个协商密钥的过程核心是ECDH密钥协商,但ECDH协商的过程前提是要对方是认证过的,可信的,前面的ECIES加密,实际上相当于对消息接收方进行认证,因为只有拥有对应的私钥才能解密消息。

创建加密连接主要流程如下:

  1. 发起端向接收端发起TCP连接,发送auth消息
  2. 接收端接受连接,解密、验证auth消息(检查recovery of signature == keccak256(ephemeral-pubk)
  3. 接收端生成auth-ack消息
  4. 接收端导出密钥,发送第一个数据帧
  5. 发起端接收到auth-ack消息,导出密钥
  6. 发起端发送第一个数据帧(代码中对应Hello packet
  7. 接收端接收并验证数据帧
  8. 发起端接收并验证数据帧
  9. 如果MAC两边都验证通过,加密握手完成。

简单概括就是:建立TCP连接-->密钥协商(auth、auth-ack)-->双发导出相同的密钥-->发送hello(协议协商)-->创建完成。具体实现需要看Ethereum源码。

分帧

auth之后的所有数据包都是分帧传输的。任何一方如果第一帧数据包验证失败都可以断开连接。

分帧传输的主要目的是在单一连接上可靠的支持多路复用协议。其次,因数据包分帧,为消息认证码产生了适当的分界点,使得加密流变得简单了。数据帧通过握手产生的密钥进行验证。

数据帧头部提供了数据包的大小和协议信息。

frame = header || header-mac || frame-data || frame-mac
header = frame-size || header-data || padding
frame-size = size of frame excluding padding, integer < 2**24, big endian
header-data = rlp.list(protocol-type[, context-id])
protocol-type = integer < 2**16, big endian
context-id = integer < 2**16, big endian
padding = zero-fill to 16-byte boundary
frame-content = any binary data header-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ header-ciphertext).digest
frame-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ left16(egress-mac.update(frame-ciphertext).digest))
egress-mac = keccak256 state, continuously updated with egress bytes
ingress-mac = keccak256 state, continuously updated with ingress bytes left16(x) is the first 16 bytes of x
|| is concatenate
^ is xor

对发送与接收的密文数据不断更新egress-macingress-mac实现消息认证;对头部数据,是通过将加密输出数据的头部与相应的mac进行异或运算(参见header-mac)。这样做是为了确保对明文mac和密文执行统一操作。所有的mac都是以明文形式发送的。

填充字节用于防止缓存区饥饿,使得帧组件按指定区块字节大小对齐。

已知的问题

  • RLPx握手被认为是易破解的,因为aes-secretmac-secret被重复用于读取和写入

    。RLPx连接的两端从相同的密钥,nonce和IV生成两个CTR流。如果攻击者知道一个明文,他们就可以用重用的密钥流破解未知明文。
  • 帧编码提供了用于多路复用的协议类型字段protocol-type,但devp2p未使用该字段。

RLPx传输协议的前向安全性

RLPx使用了(PerfectForwardSecrecy),简单来说。链接的两方都生成随机的私钥,通过随机的私钥得到公钥。然后双方交换各自的公钥,这样双方都可以通过自己随机的私钥和对方的公钥来生成一个同样的共享密钥(shared-secret)。后续的通讯使用这个共享密钥作为对称加密算法的密钥。这样来说。如果有一天一方的私钥被泄露,也只会影响泄露之后的消息的安全性,对于之前的通讯是安全的(因为通讯的密钥是随机生成的,用完后就消失了)。

前向安全性

前向安全或前向保密FS(ForwardSecrecy),有时也被称为完美前向安全PFS(PerfectForwardSecrecy),是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。前向安全能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。如果系统具有前向安全性,就可以保证万一密码或密钥在某个时刻不慎泄露,过去已经进行的通讯依然是安全,不会受到任何影响,即使系统遭到主动攻击也是如此。

最后,需要说明一下,这篇文档对RLPx协议进行了简述,具体实现协议还是有很多细节需处理,深入请看以太坊源码。

如果文中有问题或者不对的地方,可关注微信公众号与我交流学习,欢迎指教!

以太坊RLPx传输协议的更多相关文章

  1. Geth命令用法-参数详解 and 以太坊源码文件目录

    本文是对以太坊客户端geth命令的解析 命令用法 geth [选项] 命令 [命令选项] [参数-] 版本 1.7.3-stable 命令 account 管理账户 attach 启动交互式JavaS ...

  2. 死磕以太坊源码分析之rlpx协议

    死磕以太坊源码分析之rlpx协议 本文主要参考自eth官方文档:rlpx协议 符号 X || Y:表示X和Y的串联 X ^ Y: X和Y按位异或 X[:N]:X的前N个字节 [X, Y, Z, ... ...

  3. 通俗讲解:PoW共识机制与以太坊的关系、Ghost协议 及 PoS共识机制的变种---Casper

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  4. 以太坊中的Ghost协议

    https://blog.csdn.net/t46414704152abc/article/details/81191804 写得超好,终于弄懂了什么是叔块,怎么确定哪条链最长,以太坊与比特币出块的差 ...

  5. 使用 Go-Ethereum 1.7.2搭建以太坊私有链

    目录 [toc] 1.什么是Ethereum(以太坊) 以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约.开源的底层系统,以太坊从诞生到2017年5月,短短3年半时间,全球 ...

  6. C#以太坊基础入门

    在这一部分,我们将使用C#开发一个最简单的.Net控制台应用,来接入以太坊节点,并打印 所连接节点旳版本信息.通过这一部分的学习,你将掌握以下技能: 如何使用节点仿真器 如何在命令行访问以太坊节点 如 ...

  7. android和java以太坊开发区块链应用使用web3j类库

    如何使用web3j为Java应用或Android App增加以太坊区块链支持,教程内容即涉及以太坊中的核心概念,例如账户管理包括账户的创建.钱包创建.交易转账,交易与状态.智能合约开发与交互.过滤器和 ...

  8. 转:使用 Go-Ethereum 1.7.2搭建以太坊私有链

    使用 Go-Ethereum 1.7.2搭建以太坊私有链 目录 [toc] 1.什么是Ethereum(以太坊) 以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约.开源的底 ...

  9. 以太坊预言机与WEB API(原创,转载请说明原址)

    什么是预言机? 从链外获得数据,提供区块链与现实世界事件之间的连接,提供外部信息的平台 预言机自身也是一种智能合约,它允许区块链连接到任何现有的API 是这个预言机去调用各种 WEB API的接口 这 ...

随机推荐

  1. 前端js倒计时(精确到毫秒)

    话不多说,直接上代码: 有需要直接拿走, <html> <head> <style> div{ width:100%; text-align:center; fon ...

  2. Redis Ubuntu 安装

    1.使用 root 用户登录 Ubuntu  2. wget http://download.redis.io/releases/redis-5.0.3.tar.gz 下载最新的稳定版本到 redis ...

  3. JDK源码分析系列---String,StringBuilder,StringBuffer

    JDK源码分析系列---String,StringBuilder,StringBuffer 1.String public final class String implements java.io. ...

  4. 浅说——树形DP

    啊!DP! 顾名思义,树形DP就是在树上所做的动态规划.我们一般所做的动态规划多是线性的,线性DP我们可以从前向后或从后向前两种方法,不妨类比一下,在树上我们同样可以有两种方法,从根向树叶或者从树叶向 ...

  5. 我竟然不再抗拒 Java 的类加载机制了

    很长一段时间里,我对 Java 的类加载机制都非常的抗拒,因为我觉得太难理解了.但为了成为一名优秀的 Java 工程师,我决定硬着头皮研究一下. 01.字节码 在聊 Java 类加载机制之前,需要先了 ...

  6. mysql重复数据下,删除一条重复数据

    delete from information where id in (select id from (select max(id) as id,count(*) as ccc from infor ...

  7. MQ初窥门径【面试必看的Kafka和RocketMQ存储区别】

    MQ初窥门径 全称(message queue)消息队列,一个用于接收消息.存储消息并转发消息的中间件 应用场景 用于解决的场景,总之是能接收消息并转发消息 用于异步处理,比如A服务做了什么事情,异步 ...

  8. 有意思的 CDN

    Clean Clean false 7.8 磅 0 2 false false false EN-US ZH-CN AR-SA /* Style Definitions */ table.MsoNor ...

  9. MySQL数据库设计与开发规范

    目录 1. 规范背景与目的 2. 设计规范 2.1. 数据库设计 2.1.1. 库名 2.1.2. 表结构 2.1.3. 列数据类型优化 2.1.4. 索引设计 2.1.5. 分库分表.分区表 2.1 ...

  10. c++字符数组

    题目描述 题目描述 输入一个英文句子(长度不会超过100),和他的长度,统计每个字母出现的个数. 输入 第一行包括一个整数,表示句子的长度,长度不会超过100.数字后可能会有多余的无效字符 请gets ...