注意
网络部署和测试正在进行中。
可能进行小幅修订。
参见 SPEC
获取官方规范。
概述
本文档提议对隧道构建消息的加密方式进行修改,
使用 ECIES-X25519
引入的密码原语。
这是整体提案 Proposal 156
的一部分,
旨在将路由器从 ElGamal 迁移至 ECIES-X25519 密钥。
为实现网络从 ElGamal + AES256 到 ECIES + ChaCha20 的过渡,
需要支持包含混合 ElGamal 和 ECIES 路由器的隧道。
本文档提供了处理混合隧道跳点的规范。
ElGamal 跳点的格式、处理和加密方式不会做任何更改。
使用 ElGamal 的隧道创建者需要为每个跳点生成临时的 X25519 密钥对,
并遵循本规范创建包含 ECIES 跳点的隧道。
本提案指定了实现 ECIES-X25519 隧道构建所需的变化。
如需了解 ECIES 路由器所需的所有变更概览,请参见提案 156 Proposal 156
。
本提案保持了隧道构建记录的大小不变,
以确保兼容性。更小的构建记录和消息将在后续实现——参见 Proposal 157
。
密码原语
不引入新的密码原语。实现本提案所需的密码原语包括:
- AES-256-CBC,见 Cryptography
- STREAM ChaCha20/Poly1305 函数:
ENCRYPT(k, n, plaintext, ad) 和 DECRYPT(k, n, ciphertext, ad) —— 见 NTCP2 ECIES-X25519 和 RFC-7539 - X25519 DH 函数 —— 见 NTCP2 和 ECIES-X25519
- HKDF(salt, ikm, info, n) —— 见 NTCP2 和 ECIES-X25519
其他在别处定义的 Noise 函数:
- MixHash(d) —— 见 NTCP2 和 ECIES-X25519
- MixKey(d) —— 见 NTCP2 和 ECIES-X25519
目标
- 提高密码操作速度
- 使用 ECIES 原语替换 ElGamal + AES256/CBC,用于 Tunnel BuildRequestRecords 和 BuildReplyRecords
- 加密后的 BuildRequestRecords 和 BuildReplyRecords 大小不变(528 字节),以确保兼容性
- 不引入新的 I2NP 消息
- 保持加密构建记录的大小以确保兼容性
- 为隧道构建消息添加前向保密性
- 添加认证加密
- 检测构建请求记录的跳点重排序
- 提高时间戳分辨率,以便减少 Bloom 过滤器的大小
- 添加隧道过期字段,以支持可变隧道生命周期(仅限全 ECIES 隧道)
- 添加可扩展的选项字段以支持未来功能
- 复用现有密码原语
- 在保持兼容性的同时尽可能提升隧道构建消息的安全性
- 支持混合 ElGamal/ECIES 对等体的隧道
- 增强对构建消息“标记”攻击的防御能力
- 跳点在处理构建消息时无需提前知道下一跳的加密类型,
因为此时可能尚未获取下一跳的 RI - 最大限度保持与当前网络的兼容性
- 不改变 ElGamal 路由器的隧道 AES 请求/回复加密
- 不改变隧道 AES “层” 加密,相关内容见 Proposal 153
- 继续支持 8 条记录的 TBM/TBRM 和可变大小的 VTBM/VTBRM
- 不要求全网“旗帜日”式升级
非目标
- 重新设计隧道构建消息,要求“旗帜日”式升级
- 缩小隧道构建消息(需要全 ECIES 跳点和新提案)
- 使用 Proposal 143 中定义的隧道构建选项,仅小消息需要
- 双向隧道 —— 参见 Proposal 119
- 更小的隧道构建消息 —— 参见 Proposal 157
威胁模型
设计目标
任何跳点都无法确定隧道的发起者。
中间跳点必须无法确定隧道的方向或其在隧道中的位置。
任何跳点都无法读取其他请求或回复记录的内容,
除了下一跳的截断路由器哈希和临时密钥。出站构建的回复隧道成员无法读取任何回复记录。
入站构建的出站隧道成员无法读取任何请求记录,
但 OBEP 可以看到下一跳的截断路由器哈希和临时密钥(IBGW)
标记攻击
隧道构建设计的主要目标之一是使共谋路由器 X 和 Y 更难确认它们处于同一隧道中。
如果路由器 X 在第 m 跳,路由器 Y 在第 m+1 跳,它们显然会知道;
但如果路由器 X 在第 m 跳,而路由器 Y 在第 m+n 跳(n>1),这应困难得多。
标记攻击是指中间跳点 X 以某种方式修改隧道构建消息,
使得路由器 Y 在收到消息时能够检测到该修改。
目标是使任何被修改的消息在到达路由器 Y 之前被 X 和 Y 之间的某个路由器丢弃。
对于未被丢弃而到达路由器 Y 的修改,隧道创建者应在回复中检测到损坏并丢弃该隧道。
可能的攻击方式:
- 修改一条构建记录
- 替换一条构建记录
- 添加或删除一条构建记录
- 重新排序构建记录
TODO: 当前设计是否能防止所有这些攻击?
设计
Noise 协议框架
本提案基于 Noise 协议框架 NOISE
(修订版 34,2018-07-11)提出要求。
在 Noise 术语中,Alice 是发起方,Bob 是响应方。
本提案基于 Noise 协议 Noise_N_25519_ChaChaPoly_SHA256。
该 Noise 协议使用以下原语:
单向握手模式:N
Alice 不向 Bob 发送其静态密钥(N)DH 函数:X25519
X25519 DH,密钥长度为 32 字节,如 RFC-7748 所定义。加密函数:ChaChaPoly
AEAD_CHACHA20_POLY1305,如 RFC-7539 第 2.8 节所定义。
使用 12 字节 nonce,前 4 字节设为零。
与 NTCP2 中的相同。哈希函数:SHA256
标准 32 字节哈希,I2P 中已广泛使用。
对框架的补充
无。
握手模式
握手使用 Noise 握手模式。
使用以下字母映射:
- e = 一次性临时密钥
- s = 静态密钥
- p = 消息载荷
构建请求与 Noise N 模式相同。
这也与 NTCP2
中 XK 模式的第一条消息(会话请求)相同。
<- s
...
e es p ->
请求加密
构建请求记录由隧道创建者生成,并使用非对称加密发送给各个跳点。
当前的请求记录非对称加密使用 ElGamal,定义见 Cryptography
,
并包含 SHA-256 校验和。该设计不具备前向保密性。
新设计将使用单向 Noise 模式“N”,结合 ECIES-X25519 临时-静态 DH、HKDF 和
ChaCha20/Poly1305 AEAD,以实现前向保密、完整性和认证。
Alice 是隧道构建请求方。隧道中的每个跳点都是 Bob。
(载荷安全属性)
N: 认证 保密性
-> e, es 0 2
认证:无(0)。
该载荷可能由任何方发送,包括主动攻击者。
保密性:2。
加密至已知接收方,仅对发送方密钥泄露具有前向保密性,易受重放攻击。
该载荷仅基于涉及接收方静态密钥对的 DH 进行加密。
如果接收方的静态私钥被泄露(即使在将来),该载荷可被解密。
此消息也可被重放,因为接收方没有临时密钥贡献。
"e":Alice 生成新的临时密钥对并存储在 e 变量中,将临时公钥以明文写入消息缓冲区,
并将公钥与旧的 h 一起哈希以派生新的 h。
"es":在 Alice 的临时密钥对和 Bob 的静态密钥对之间执行 DH。
结果与旧的 ck 一起哈希以派生新的 ck 和 k,n 设为零。
回复加密
构建回复记录由跳点创建者生成,并使用对称加密发送回创建者。
当前的回复记录对称加密使用 AES,并前置 SHA-256 校验和。
该设计不具备前向保密性。
新设计将使用 ChaCha20/Poly1305 AEAD 实现完整性与认证。
理由
请求中的临时公钥无需使用 AES 或 Elligator2 混淆。
只有前一跳能看到它,而前一跳已知下一跳是 ECIES。
回复记录无需使用另一个 DH 进行完整的非对称加密。
规范
构建请求记录
加密的 BuildRequestRecords 对 ElGamal 和 ECIES 均为 528 字节,以确保兼容性。
请求记录未加密(ElGamal)
作为参考,这是来自 I2NP
的 ElGamal 路由器当前隧道 BuildRequestRecord 规范。
未加密数据在加密前会前置一个非零字节和数据的 SHA-256 哈希,
定义见 Cryptography
。
所有字段均为大端序。
未加密大小:222 字节
字节 0-3:接收消息的隧道 ID,非零
字节 4-35:本地路由器身份哈希
字节 36-39:下一跳隧道 ID,非零
字节 40-71:下一跳路由器身份哈希
字节 72-103:AES-256 隧道层密钥
字节 104-135:AES-256 隧道 IV 密钥
字节 136-167:AES-256 回复密钥
字节 168-183:AES-256 回复 IV
字节 184:标志位
字节 185-188:请求时间(自纪元以来的小时数,向下取整)
字节 189-192:下一消息 ID
字节 193-221:未解释 / 随机填充
请求记录已加密(ElGamal)
作为参考,这是来自 I2NP 的 ElGamal 路由器当前隧道 BuildRequestRecord 规范。
加密大小:528 字节
字节 0-15:跳点的截断身份哈希
字节 16-528:ElGamal 加密的 BuildRequestRecord
请求记录未加密(ECIES)
这是为 ECIES-X25519 路由器提议的隧道 BuildRequestRecord 规范。
变更摘要:
- 移除未使用的 32 字节路由器哈希
- 将请求时间从小时改为分钟
- 添加过期字段以支持未来可变隧道时间
- 增加标志位空间
- 添加映射以支持额外构建选项
- 跳点自身的回复记录不使用 AES-256 回复密钥
- 未加密记录更长,因为加密开销更小
请求记录不包含任何 ChaCha 回复密钥。
这些密钥由 KDF 派生。见下文。
所有字段均为大端序。
未加密大小:464 字节
字节 0-3:接收消息的隧道 ID,非零
字节 4-7:下一跳隧道 ID,非零
字节 8-39:下一跳路由器身份哈希
字节 40-71:AES-256 隧道层密钥
字节 72-103:AES-256 隧道 IV 密钥
字节 104-135:AES-256 回复密钥
字节 136-151:AES-256 回复 IV
字节 152:标志位
字节 153-155:更多标志位,未使用,为兼容性设为 0
字节 156-159:请求时间(自纪元以来的分钟数,向下取整)
字节 160-163:请求过期时间(自创建以来的秒数)
字节 164-167:下一消息 ID
字节 168-x:隧道构建选项(映射)
字节 x-x:由标志位或选项隐含的其他数据
字节 x-463:随机填充
标志位字段定义见 Tunnel Creation ,包含以下内容:
位顺序:76543210(位 7 为最高位)
位 7:若设置,则允许任何人发送消息
位 6:若设置,则允许向任何人发送消息,并将回复发送到
隧道构建回复消息中指定的下一跳
位 5-0:未定义,为兼容未来选项必须设为 0
位 7 表示该跳点将作为入站网关(IBGW)。位 6
表示该跳点将作为出站端点(OBEP)。若两者均未设置,
该跳点为中间参与者。两者不能同时设置。
请求过期时间用于未来可变隧道持续时间。
目前唯一支持的值是 600(10 分钟)。
隧道构建选项是 Common Structures
中定义的映射结构。
供将来使用。目前未定义任何选项。
若映射结构为空,则为两个字节 0x00 0x00。
映射的最大大小(包括长度字段)为 296 字节,
映射长度字段的最大值为 294。
请求记录已加密(ECIES)
除临时公钥为小端序外,所有字段均为大端序。
加密大小:528 字节
字节 0-15:跳点的截断身份哈希
字节 16-47:发送方的临时 X25519 公钥
字节 48-511:ChaCha20 加密的 BuildRequestRecord
字节 512-527:Poly1305 MAC
构建回复记录
加密的 BuildReplyRecords 对 ElGamal 和 ECIES 均为 528 字节,以确保兼容性。
回复记录未加密(ElGamal)
ElGamal 回复使用 AES 加密。
所有字段均为大端序。
未加密大小:528 字节
字节 0-31:字节 32-527 的 SHA-256 哈希
字节 32-526:随机数据
字节 527:回复
总长度:528
回复记录未加密(ECIES)
这是为 ECIES-X25519 路由器提议的隧道 BuildReplyRecord 规范。
变更摘要:
- 添加构建回复选项的映射
- 未加密记录更长,因为加密开销更小
ECIES 回复使用 ChaCha20/Poly1305 加密。
所有字段均为大端序。
未加密大小:512 字节
字节 0-x:隧道构建回复选项(映射)
字节 x-x:由选项隐含的其他数据
字节 x-510:随机填充
字节 511:回复字节
隧道构建回复选项是 Common Structures
中定义的映射结构。
供将来使用。目前未定义任何选项。
若映射结构为空,则为两个字节 0x00 0x00。
映射的最大大小(包括长度字段)为 511 字节,
映射长度字段的最大值为 509。
回复字节为以下值之一,
定义见 Tunnel Creation
,以避免指纹识别:
- 0x00(接受)
- 30(TUNNEL_REJECT_BANDWIDTH)
回复记录已加密(ECIES)
加密大小:528 字节
字节 0-511:ChaCha20 加密的 BuildReplyRecord
字节 512-527:Poly1305 MAC
在完全过渡到 ECIES 记录后,填充规则与请求记录相同。
记录的对称加密
允许并需要混合隧道,以实现从 ElGamal 到 ECIES 的过渡。
在过渡期间,越来越多的路由器将使用 ECIES 密钥。
对称密码预处理将以相同方式运行:
“加密”:
- 密码运行在解密模式
- 在预处理中预先解密请求记录(隐藏加密的请求记录)
“解密”:
- 密码运行在加密模式
- 跳点通过加密揭示下一个明文请求记录
ChaCha20 没有“模式”,因此只需运行三次:
- 预处理时一次
- 跳点处理时一次
- 最终回复处理时一次
当使用混合隧道时,隧道创建者需要根据当前和前一跳的加密类型
来决定 BuildRequestRecord 的对称加密方式。
每个跳点将使用自己的加密类型来加密 BuildReplyRecords,
以及 VariableTunnelBuildMessage (VTBM) 中的其他记录。
在回复路径上,端点(发送方)需要使用每个跳点的回复密钥
撤销 Multiple Encryption
。
作为澄清示例,考虑一个被 ElGamal 包围的 ECIES 出站隧道:
- 发送方(OBGW) -> ElGamal(H1) -> ECIES(H2) -> ElGamal(H3)
所有 BuildRequestRecords 均处于加密状态(使用 ElGamal 或 ECIES)。
使用 AES256/CBC 时,仍对每条记录单独使用,不跨多条记录链接。
同样,ChaCha20 将用于加密每条记录,而不是在整个 VTBM 上流式加密。
请求记录由发送方(OBGW)预处理:
H3 的记录使用以下方式“加密”:
- H2 的回复密钥(ChaCha20)
- H1 的回复密钥(AES256/CBC)
H2 的记录使用以下方式“加密”:
- H1 的回复密钥(AES256/CBC)
H1 的记录无需对称加密直接发出
只有 H2 检查回复加密标志,并看到其后跟 AES256/CBC。
每个跳点处理后,记录处于“解密”状态:
H3 的记录使用以下方式“解密”:
- H3 的回复密钥(AES256/CBC)
H2 的记录使用以下方式“解密”:
- H3 的回复密钥(AES256/CBC)
- H2 的回复密钥(ChaCha20-Poly1305)
H1 的记录使用以下方式“解密”:
- H3 的回复密钥(AES256/CBC)
- H2 的回复密钥(ChaCha20)
- H1 的回复密钥(AES256/CBC)
隧道创建者(即入站端点 IBEP)对回复进行后处理:
H3 的记录使用以下方式“加密”:
- H3 的回复密钥(AES256/CBC)
H2 的记录使用以下方式“加密”:
- H3 的回复密钥(AES256/CBC)
- H2 的回复密钥(ChaCha20-Poly1305)
H1 的记录使用以下方式“加密”:
- H3 的回复密钥(AES256/CBC)
- H2 的回复密钥(ChaCha20)
- H1 的回复密钥(AES256/CBC)
请求记录密钥(ECIES)
这些密钥在 ElGamal BuildRequestRecords 中显式包含。
对于 ECIES BuildRequestRecords,隧道密钥和 AES 回复密钥包含在内,
但 ChaCha 回复密钥由 DH 交换派生。
有关路由器静态 ECIES 密钥的详细信息,参见 Proposal 156
。
以下是派生先前在请求记录中传输的密钥的方法描述。
KDF 用于初始 ck 和 h
这是标准 NOISE 对模式“N”使用标准协议名称。
这是“e”消息模式:
// 定义 protocol_name。
Set protocol_name = "Noise_N_25519_ChaChaPoly_SHA256"
(31 字节,US-ASCII 编码,无 NULL 终止)。
// 定义 Hash h = 32 字节
// 填充至 32 字节。不要哈希,因为它不超过 32 字节。
h = protocol_name || 0
定义 ck = 32 字节链式密钥。将 h 数据复制到 ck。
Set chainKey = h
// MixHash(null prologue)
h = SHA256(h);
// 到此为止,所有路由器均可预先计算。
KDF 用于请求记录
ElGamal 隧道创建者为隧道中的每个 ECIES 跳点生成一个临时 X25519 密钥对,
并使用上述方案加密其 BuildRequestRecord。
ElGamal 隧道创建者将使用本规范之前的方案加密到 ElGamal 跳点。
ECIES 隧道创建者需要使用 Tunnel Creation
中定义的方案
加密到每个 ElGamal 跳点的公钥。ECIES 隧道创建者将使用上述方案加密到 ECIES 跳点。
这意味着跳点只会看到与其自身加密类型相同的加密记录。
对于 ElGamal 和 ECIES 隧道创建者,它们将为每个 ECIES 跳点生成唯一的临时 X25519 密钥对,
用于加密到 ECIES 跳点。
重要:
临时密钥必须在每个 ECIES 跳点和每个构建记录中唯一。
若未使用唯一密钥,共谋跳点可能确认它们处于同一隧道中,从而打开攻击向量。
// 每个跳点的 X25519 静态密钥对 (hesk, hepk) 来自路由器身份
hesk = GENERATE_PRIVATE()
hepk = DERIVE_PUBLIC(hesk)
// MixHash(hepk)
// || 表示连接
h = SHA256(h || hepk);
// 到此为止,每个路由器均可为所有传入构建请求预先计算
// 发送方为 VTBM 中的每个 ECIES 跳点生成 X25519 临时密钥对 (sesk, sepk)
sesk = GENERATE_PRIVATE()
sepk = DERIVE_PUBLIC(sesk)
// MixHash(sepk)
h = SHA256(h || sepk);
“e”消息模式结束。
这是“es”消息模式:
// Noise es
// 发送方使用跳点的静态公钥执行 X25519 DH。
// 每个跳点找到其截断身份哈希对应的记录,
// 并提取加密记录前的发送方临时密钥。
sharedSecret = DH(sesk, hepk) = DH(hesk, sepk)
// MixKey(DH())
//[chainKey, k] = MixKey(sharedSecret)
// ChaChaPoly 加密/解密参数
keydata = HKDF(chainKey, sharedSecret, "", 64)
// 保存用于回复记录 KDF
chainKey = keydata[0:31]
// AEAD 参数
k = keydata[32:63]
n = 0
plaintext = 464 字节构建请求记录
ad = h
ciphertext = ENCRYPT(k, n, plaintext, ad)
“es”消息模式结束。
// MixHash(ciphertext)
// 保存用于回复记录 KDF
h = SHA256(h || ciphertext)
replyKey、layerKey 和 layerIV 仍必须包含在 ElGamal 记录中,
可随机生成。
请求记录加密(ElGamal)
定义见 Tunnel Creation
。
ElGamal 跳点的加密方式无变更。
回复记录加密(ECIES)
回复记录使用 ChaCha20/Poly1305 加密。
// AEAD 参数
k = 来自构建请求的 chainkey
n = 0
plaintext = 512 字节构建回复记录
ad = 来自构建请求的 h
ciphertext = ENCRYPT(k, n, plaintext, ad)
回复记录加密(ElGamal)
定义见 Tunnel Creation
。
ElGamal 跳点的加密方式无变更。
安全分析
ElGamal 无法为隧道构建消息提供前向保密性。
AES256/CBC 略好,仅理论上可能受到已知明文 biclique 攻击的削弱。
对 AES256/CBC 唯一已知的实际攻击是填充预言攻击,当攻击者已知 IV 时可能发生。
攻击者需要破解下一跳的 ElGamal 加密才能获取 AES256/CBC 密钥信息(回复密钥和 IV)。
ElGamal 比 ECIES 显著更消耗 CPU,可能导致资源耗尽。
ECIES 结合每构建请求记录或 VariableTunnelBuildMessage 使用新临时密钥,提供前向保密性。
ChaCha20Poly1305 提供 AEAD 加密,允许接收方在尝试解密前验证消息完整性。
理由
本设计最大限度复用现有密码原语、协议和代码。
本设计将风险降至最低。
实现说明
- 较旧的路由器不检查跳点的加密类型,会发送 ElGamal 加密的记录。
一些较新的路由器存在缺陷,会发送各种格式错误的记录。
实现者应尽可能在 DH 操作前检测并拒绝这些记录,以减少 CPU 使用。
问题
迁移
参见 Proposal 156 。