跳转到内容

Sphinx — mix 网络最紧凑的可证安全消息格式

是什么

Sphinx 是一种密码学消息包格式,让匿名通信网络中的每个中转节点只能看到”我应该把这个包交给谁”,看不到发送者是谁、路径有多长、自己处于第几跳。日常类比:把一封信套进五层信封,每层信封只写下一站地址,每个邮递员拆掉自己那层后转交,但没有人能把所有信封摆回一起还原路径。

Sphinx 于 2009 年发表时解决了一个长达二十多年的开放问题:已有方案要么体积紧凑但安全性只靠直觉(Mixmaster、Minx),要么有完整证明但头部膨胀到数千字节(CL05)。Sphinx 同时做到了两件事——用 Curve25519 椭圆曲线时,支持 5 跳路径的消息头部仅需 224 字节,比 Mixmaster 小 5 倍,且在 Universal Composability(UC)框架下有完整的可证安全归约。

关键机制是”群元素盲化”:每条消息只携带一个群元素 α,第 i 跳节点用自身私钥计算共享密钥后,将 α 乘上一个从共享密钥派生的盲化因子,传给第 i+1 跳——每跳看到的 α 都不一样,节点之间无法串通对齐同一条消息。

为什么重要

不理解 Sphinx,下面这些事都没法解释:

  • 为什么 Lightning Network 的支付路由(BOLT #4)中间节点不知道自己离收款方还有几跳——它用的就是 Sphinx 格式
  • 为什么 Nym 和 Loopix 声称能抗流量分析:Sphinx 保证了数学层面的路径位置不可区分,流量混合策略在此之上叠加
  • 为什么”洋葱路由”不等于”安全洋葱路由”——Tor 的洋葱层用的是 RSA 加密,每跳头部固定大;Sphinx 用椭圆曲线盲化,头部恒定且可证安全
  • 为什么匿名回复(SURB)需要单独设计:发送方能匿名发出,但让接收方匿名回复而不暴露发送方地址是额外的密码学难题,Sphinx 提供了完整的回复块方案

核心要点

1. 单一群元素盲化(α 的旅程)

这里的”群”是密码学中的一组满足运算规则的数字集合(可以把它想象成一个封闭的数字操场,进去了只能在里面运算)。公开的基点 g 相当于操场的起点,对 g 做 r 次”乘法”写作 g^r。每条消息头部只有一个群元素 α = g^r(r 是发送方随机选取的秘密数)。第 i 跳节点用私钥 x_i 计算 s_i = α^{x_i},从 s_i 派生流密码密钥解密自己的路由槽,再把 α 盲化为 α’ = α^{b_i}(b_i 从 s_i 派生),传给下一跳。类比:每个邮递员拿到信件后,把发件人地址用新的伪名换掉再转交,而且每次换的伪名只有当事人知道怎么推导。

2. 逐层加密 + LIONESS 防篡改追踪

路由信息用流密码逐层加密(类似洋葱剥皮),payload 用 LIONESS 整体加密——LIONESS 是”搅拌四次”的大块加密算法,任意一字节被篡改都会随机化整块内容,让攻击者无法通过标记消息再观察输出来追踪路径(这种攻击叫 tagging attack)。每跳节点解密后,路由层向左移位并补上随机填充,让下一跳的包头与上一跳看起来完全不同——节点无法判断自己是第几跳,路径长度得到隐藏。MAC(消息认证码)在每跳验证头部完整性,校验失败则直接丢弃。

3. 单次使用回复块(SURB)

接收方预先生成一个 SURB(Single-Use Reply Block):里面预打包了路径信息和一个对称密钥,交给发送方。发送方用 SURB 把回复送出,接收方用对称密钥解密。SURB 是一次性的——用完即废,重放攻击靠每节点维护的”已见标签”数据库拦截。

实践案例

案例 1:Lightning Network 支付路由

Lightning 的 BOLT #4 规范直接采用 Sphinx 为支付消息提供源路由隐私。发送方选定整条路径(最多 20 跳),构建 Sphinx 包头,每个路由节点只能解密自己负责的那一层:

# 简化版:发送方构建 Sphinx 头部(伪代码)
def build_sphinx_header(path_nodes, payload):
r = random_scalar()
alpha = G * r # 初始群元素
# 逐节点预计算共享密钥
shared_keys = []
blind = r
for node_pubkey in path_nodes:
s = node_pubkey * blind # 共享密钥 s_i = pk_i^{x_i}
b = hash_to_scalar(s) # 盲化因子 b_i
blind = (blind * b) % order # 累积盲化
shared_keys.append(s)
# 逐层加密路由信息(从最后一跳往前)
routing = encrypt_layers(path_nodes, shared_keys, payload)
return alpha, routing # 头部 = alpha (32B) + routing (固定长度)

中间节点只需:计算 s = alpha^{私钥},解密自己的路由槽,提取下一跳地址,盲化 alpha,转发——整个过程无需接触私钥以外的全局状态。

案例 2:Nym 匿名消息网络

Nym 用 Sphinx 封装每条用户消息,在三层 mix 节点网络中路由。每条消息的大小被填充到固定长度(Sphinx 保证 payload 可变但头部恒定),流量分析者看不出消息内容或路径关联:

# mix 节点处理一个 Sphinx 包(伪代码)
def node_process(packet, private_key):
alpha, beta, gamma = packet.header, packet.routing, packet.mac
# 1. 计算共享密钥并验 MAC
s = alpha ** private_key # ECDH
if not verify_mac(gamma, s, beta):
raise ReplayOrTamperError
# 2. 标签去重(防重放)
tag = hmac(s, b"replay-tag")
if tag in seen_tags:
drop(); return
seen_tags.add(tag)
# 3. 解密路由槽,提取下一跳
keystream = prg(s)
routing_decrypted = xor(beta, keystream)
next_hop, rest_routing = routing_decrypted[:ADDR_LEN], routing_decrypted[ADDR_LEN:]
# 4. 盲化 alpha,更新包头,转发
b = hash_to_scalar(s)
new_alpha = alpha ** b
forward(next_hop, packet_with(new_alpha, rest_routing + random_padding()))

案例 3:匿名邮件回复(SURB 场景)

Alice 想收到 Bob 的匿名回复,但不想让路径中的节点知道 Bob 在回复她:

# Alice 预生成 SURB,发给 Bob
surb_key = random_bytes(32)
surb = build_surb(
path=[mix1, mix2, mix3, alice_mailbox],
dest_key=surb_key # Alice 用这个密钥解密回复
)
alice_sends_to_bob(surb)
# Bob 用 SURB 发回复
ciphertext = encrypt(surb_key, b"Hello Alice!")
# Bob 把 SURB 头部作为路由信息,ciphertext 作为 payload,发出匿名消息
send_with_surb(surb, ciphertext)
# Alice 收到后,用 surb_key 解密,无需知道 Bob 的身份
reply = decrypt(surb_key, received_ciphertext)

SURB 的一次性特性意味着 Alice 必须为每次期望的回复预先分发一个新的 SURB。

踩过的坑

  1. 重放检测数据库随密钥生命周期绑定:每个节点必须维护”已见消息标签”数据库,只有轮换私钥时才能清空——密钥轮换间隔决定存储开销,间隔越长存储越大,间隔越短前向安全越好,但会让旧消息失效。

  2. SURB 是严格一次性的:接收方每次期望回复都需预先发出一个新 SURB,在大量回复场景下需要维护”SURB 池”,池耗尽则发送方无法得到回复,协议层没有自动补发机制。

  3. Sphinx 只管包格式,不管流量分析:路径位置和消息内容被隐藏,但节点能观察到”流量到了、流量发出”的时序关联——必须配合批处理混合策略(stop-and-go mix、连续时间 mix)才能抵御全局观察者。

  4. 所有节点共享一个可信 PKI:发送方选路径需要知道每跳节点的公钥,PKI 的可信度和节点公钥的新鲜度完全在 Sphinx 协议保证范围之外,是系统级额外假设——PKI 失陷等于匿名失陷。

适用 vs 不适用场景

适用

  • 需要强匿名性的消息转发网络(mix 网络、匿名 remailer)
  • 支付通道网络的源路由隐私(Lightning Network BOLT #4 兼容场景)
  • 低带宽敏感的匿名通信(Sphinx 头部是同类方案中最小的)
  • 需要形式化安全证明的合规场景(Sphinx 有完整 UC 归约)

不适用

  • 需要低延迟实时通信(mix 网络引入的批量延迟与 Sphinx 设计无关,但部署 Sphinx 的系统通常都有此代价)
  • 点对点直连场景(不经过 mix 节点的通信不需要 Sphinx 格式)
  • 需要多路复用或分片(Sphinx 是单包格式,长消息分片和重组在协议层之上处理)
  • 资源极度受限的嵌入式设备(椭圆曲线标量乘法和 LIONESS 加密有计算开销)

历史小故事(可跳过)

  • 1981 年:David Chaum 提出 mix 网络概念,用 RSA 公钥加密构建匿名消息转发,定义了这一领域。
  • 2003 年:Mixminion 尝试在 Mixmaster 基础上加入匿名回复,但头部仍需 848 字节,安全性证明不完整。
  • 2005 年:Camenisch-Lysyanskaya 设计了可证明安全的 onion 加密(CL05),但头部达数千字节,实用性受限。
  • 2009 年:Danezis 和 Goldberg 在 IEEE Symposium on Security and Privacy(Oakland)发表 Sphinx,用椭圆曲线盲化一举解决紧凑与可证安全的矛盾,头部缩至 224 字节。
  • 2016-2019 年:Lightning Network BOLT #4、Loopix、Nym 相继采用 Sphinx 作为底层包格式,使其从学术方案变成实际部署的互联网基础设施。
  • 2023 年:研究者发现原始 Sphinx 安全证明中存在一处技术漏洞(关于 payload 的不可区分性),Everspaugh 等人发表完整修复版证明,Sphinx 的安全基础得到加固。

学到什么

  1. 紧凑与可证安全不矛盾——Sphinx 用”单一群元素 + 盲化”替代”每跳一个公钥”,头部从 KB 压到百字节,同时保住了完整安全归约
  2. 盲化是隐藏路径位置的核心技巧——每跳对 α 做乘法变换,让节点间无法通过比较 α 值来追踪同一条消息
  3. 协议层和系统层的匿名保证要分开算——Sphinx 只保证包格式层的密码学性质,流量分析防护需要系统层额外设计
  4. 一次性设计(SURB)是可用性瓶颈——密码学上最简洁的回复方案,工程上需要额外的 SURB 管理机制

延伸阅读

关联

  • chaum-1981-mix —— 定义了匿名消息转发问题,Sphinx 是其 28 年后的紧凑可证安全答案
  • tor-2004 —— 同为洋葱路由,Tor 侧重低延迟、启发式安全;Sphinx 侧重可证安全、紧凑包格式
  • curve25519-2006 —— Sphinx 推荐的椭圆曲线实现,32 字节群元素是 224 字节头部的来源
  • lioness-1996 —— Sphinx payload 加密用的大块 PRP,抗 tagging 攻击的关键组件
  • canetti-uc-2001 —— Sphinx 安全证明所用的 Universal Composability 框架

反向链接

  • chaum-1981-mix —— Chaum Mix Network — 把匿名通信从理论变成工程
  • piotrowska-loopix-2017 —— Loopix — 低延迟 mix 网络实现发送方和接收方双向匿名
  • tor-2004 —— Tor 洋葱路由 — 让你的网络请求穿上三层马甲