Rekor 透明日志完整教程 / 04 - 架构详解
第 4 章:架构详解
本章深入剖析 Rekor 的内部架构,从 Merkle Tree 的数学原理到 Trillian 的工程实现,帮助你理解透明日志为什么能提供密码学级别的不可篡改保证。
4.1 整体架构
4.1.1 Rekor 系统架构图
┌────────────────────────────────────────────────────────────────────────┐
│ Rekor 系统架构 │
│ │
│ ┌─────────────┐ ┌──────────────────────────────────────────────┐ │
│ │ rekor-cli │────►│ rekor-server (API) │ │
│ │ cosign │◄────│ │ │
│ │ 其他客户端 │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ └─────────────┘ │ │ API 层 │ │ 条目类型 │ │ 验证逻辑 │ │ │
│ │ │ (REST) │ │ 处理器 │ │ │ │ │
│ │ └────┬─────┘ └──────────┘ └───────────┘ │ │
│ └───────┼──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Trillian │ ← Google 透明日志框架 │
│ │ │ │
│ │ ┌───────────┐ │ │
│ │ │ Log │ │ ← Merkle Tree 存储 │
│ │ │ Server │ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ Log │ │ ← 数据库存储后端 │
│ │ │ Signer │ │ │
│ │ └─────┬─────┘ │ │
│ └───────┼───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ MySQL / PgSQL │ ← 持久化存储 │
│ └──────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ Redis │ ← 缓存(可选) │
│ └──────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
4.1.2 组件职责
| 组件 | 职责 | 关键特性 |
|---|---|---|
| rekor-cli / cosign | 客户端,发起请求 | 轻量、跨平台 |
| rekor-server | API 服务,处理业务逻辑 | RESTful API、条目类型路由 |
| Trillian Log Server | Merkle Tree 管理 | 追加操作、哈希计算 |
| Trillian Log Signer | 签名树头(Tree Head) | 保证日志的可验证性 |
| MySQL / PostgreSQL | 持久化存储 | 叶子节点、中间节点 |
| Redis | 缓存 | 加速读取、速率限制 |
4.2 Merkle Tree 深入解析
4.2.1 Merkle Tree 的数学基础
Merkle Tree(默克尔树),也称为哈希树(Hash Tree),是一种基于哈希函数的树形数据结构。
定义:给定 n 个数据块 $d_1, d_2, …, d_n$,Merkle Tree 通过以下方式构建:
叶子节点:H(d_i) (i = 1, 2, ..., n)
内部节点:H(left_child || right_child)
根节点:H(root_left || root_right)
其中 H 是密码学安全的哈希函数(如 SHA-256),|| 表示拼接。
4.2.2 树的构建过程
以 8 个数据块为例:
层次 3 (根): H(H₀₁ || H₂₃)
┌────────┴────────┐
层次 2: H(H₀ || H₁) H(H₂ || H₃)
┌────┴────┐ ┌────┴────┐
层次 1: H(d₀) H(d₁) H(d₂) H(d₃)
│ │ │ │
层次 0: d₀ d₁ d₂ d₃
构建算法:
import hashlib
def sha256(data: bytes) -> bytes:
return hashlib.sha256(data).digest()
def build_merkle_tree(leaves: list[bytes]) -> bytes:
"""构建 Merkle Tree 并返回根哈希"""
if not leaves:
return sha256(b'')
# 计算叶子哈希
current_level = [sha256(leaf) for leaf in leaves]
# 自底向上构建树
while len(current_level) > 1:
next_level = []
for i in range(0, len(current_level), 2):
left = current_level[i]
# 如果节点数是奇数,复制最后一个节点
right = current_level[i + 1] if i + 1 < len(current_level) else left
next_level.append(sha256(left + right))
current_level = next_level
return current_level[0]
4.2.3 包含证明(Inclusion Proof)
包含证明用于证明某个叶子节点存在于 Merkle Tree 中,复杂度仅为 O(log n)。
证明路径:从叶子节点到根节点的路径上,每个节点的兄弟节点哈希值。
验证 d₁ 存在的证明路径: [H(d₀), H₂₃]
计算过程:
step 1: H₀₁ = H(H(d₀) || H(d₁)) ← 使用证明中的 H(d₀)
step 2: root = H(H₀₁ || H₂₃) ← 使用证明中的 H₂₃
step 3: 比较 root 是否等于已知的根哈希
验证算法:
def verify_inclusion(
leaf_hash: bytes, # 叶子节点哈希
proof_hashes: list[bytes], # 证明路径(兄弟节点哈希)
leaf_index: int, # 叶子节点索引
root_hash: bytes, # 预期的根哈希
tree_size: int # 树大小
) -> bool:
"""验证包含证明"""
current_hash = leaf_hash
index = leaf_index
for sibling_hash in proof_hashes:
if index % 2 == 0:
# 当前节点是左子节点,兄弟在右边
current_hash = sha256(current_hash + sibling_hash)
else:
# 当前节点是右子节点,兄弟在左边
current_hash = sha256(sibling_hash + current_hash)
index //= 2
return current_hash == root_hash
4.2.4 一致性证明(Consistency Proof)
一致性证明用于证明两个不同时间点的 Merkle Tree 状态是一致的(即新的树是旧的树的追加扩展)。
时间 T1 的树 (size=4): 时间 T2 的树 (size=7):
Root₁ Root₂
/ \ / \
H₀₁ H₂₃ H₀₁₃ H₄₆
/ \ / \ / \ / \
H₀ H₁ H₂ H₃ H₀₁ H₂₃ H₄ H₅₆
/ \ / \
H₂ H₃ H₅ H₆
一致性证明: [H₂₃, H₀₁]
- 证明 T1 的 Root₁ 包含在 T2 的 Root₂ 中
4.2.5 安全性分析
| 属性 | 保证 | 攻击难度 |
|---|---|---|
| 抗原像攻击 | 给定哈希值,无法找到原始数据 | O(2^256) |
| 抗第二原像攻击 | 给定数据,无法找到另一个具有相同哈希的数据 | O(2^256) |
| 抗碰撞攻击 | 无法找到两个具有相同哈希的不同数据 | O(2^128) |
| 篡改检测 | 任何修改都会改变根哈希 | 即时检测 |
4.3 Trillian 框架
4.3.1 Trillian 是什么?
Trillian 是 Google 开发的一个透明日志框架,最初用于 Certificate Transparency(CT)日志。Rekor 基于 Trillian 构建,利用其提供的 Merkle Tree 管理能力。
4.3.2 Trillian 架构
┌──────────────────────────────────────────────────────────────┐
│ Trillian 架构 │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Trillian Log Server │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ │
│ │ │ Sequencer │ │ Read API │ │ Admin API │ │ │
│ │ │ (排序器) │ │ (读取接口) │ │ (管理接口) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────────────┘ │ │
│ └─────────┼───────────────┼────────────────────────────┘ │
│ │ │ │
│ ┌─────────▼───────────────▼────────────────────────────┐ │
│ │ Trillian Storage Interface │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ MySQL │ │ PostgreSQL│ │ Cloud Spanner │ │ │
│ │ │ (默认) │ │ │ │ (Google Cloud) │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Trillian Log Signer │ │
│ │ │ │
│ │ - 定期将待处理的叶子节点合并到 Merkle Tree │ │
│ │ - 计算新的根哈希 │ │
│ │ - 对树头(Tree Head)进行签名 │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
4.3.3 Trillian 核心概念
| 概念 | 说明 |
|---|---|
| Log Tree | 透明日志树,只支持追加操作 |
| Map Tree | 透明映射树,支持键值查询(Rekor 不使用) |
| Leaf | 叶子节点,存储实际数据的哈希 |
| Tree Head | 树的当前状态(根哈希 + 大小 + 签名) |
| Sequencer | 排序器,将新叶子节点批量添加到树中 |
| Signed Tree Head (STH) | 签名的树头,包含根哈希和数字签名 |
4.3.4 数据库表结构
Trillian 使用以下核心表:
-- 叶子节点表
CREATE TABLE LeafData (
TreeID BIGINT NOT NULL,
LeafID VARBINARY(255) NOT NULL,
LeafValue BLOB NOT NULL,
ExtraData BLOB,
PRIMARY KEY (TreeID, LeafID)
);
-- Merkle 节点表
CREATE TABLE Subtree (
TreeID BIGINT NOT NULL,
SubtreeID VARBINARY(255) NOT NULL,
Nodes BLOB NOT NULL,
PRIMARY KEY (TreeID, SubtreeID)
);
-- 树头表
CREATE TABLE TreeHead (
TreeID BIGINT NOT NULL,
TreeHeadTimestamp BIGINT NOT NULL,
TreeSize BIGINT,
RootHash VARBINARY(255),
RootSignature VARBINARY(1024),
PRIMARY KEY (TreeID, TreeHeadTimestamp)
);
-- 待处理的叶子节点
CREATE TABLE Unsequenced (
TreeID BIGINT NOT NULL,
Bucket INT NOT NULL,
LeafID VARBINARY(255) NOT NULL,
QueueTimestampNanos BIGINT NOT NULL,
PRIMARY KEY (TreeID, Bucket, QueueTimestampNanos, LeafID)
);
4.4 Rekor Server 详解
4.4.1 请求处理流程
客户端请求 Rekor Server Trillian
│ │ │
│ POST /api/v1/log/entries │ │
│ ─────────────────────────► │ │
│ │ │
│ │ 1. 解析请求 │
│ │ 2. 识别条目类型 │
│ │ 3. 验证签名格式 │
│ │ 4. 计算条目哈希 │
│ │ │
│ │ QueueLeaf (添加叶子) │
│ │ ─────────────────────────────► │
│ │ │
│ │ ◄─────────────────────────────│
│ │ 返回叶子状态 │
│ │ │
│ │ 5. 等待包含到树中 │
│ │ 6. 获取包含证明 │
│ │ │
│ 201 Created + 条目 │ │
│ ◄───────────────────────── │ │
4.4.2 条目类型处理
Rekor 支持多种条目类型,每种类型都有对应的处理器:
// 简化的条目类型注册逻辑
package types
var TypeMap = map[string]EntryFactory{
"hashedrekord": NewHashedRekord,
"intoto": NewIntoto,
"dsse": NewDSSE,
"alpine": NewAlpine,
"cose": NewCose,
"jar": NewJar,
"helm": NewHelm,
"rfc3161": NewRFC3161,
"rpm": NewRPM,
"tuf": NewTUF,
"x509": NewX509,
}
4.4.3 条目哈希计算
每个条目在写入前会被序列化并计算哈希,作为 Merkle Tree 的叶子值:
// 条目哈希计算(简化)
func EntryToLeaf(entry Entry) (*trillian.LogLeaf, error) {
// 1. 序列化条目
payload, err := entry.Canonicalize()
if err != nil {
return nil, err
}
// 2. 计算哈希
hash := sha256.Sum256(payload)
// 3. 创建叶子节点
return &trillian.LogLeaf{
LeafValue: payload,
LeafIdentityHash: hash[:],
}, nil
}
4.5 不可篡改性分析
4.5.1 为什么 Rekor 是不可篡改的?
Rekor 的不可篡改性来源于以下几个层面的保护:
┌─────────────────────────────────────────────────────────────┐
│ 不可篡改性保证层级 │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 层级 1: Merkle Tree 结构 │ │
│ │ - 修改任何叶子节点都会改变根哈希 │ │
│ │ - 根哈希变化会被所有验证者检测到 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 层级 2: 签名的树头 (Signed Tree Head) │ │
│ │ - Trillian Log Signer 对每个树头进行数字签名 │ │
│ │ - 攻击者需要伪造签名才能修改历史 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 层级 3: 公开见证 (Public Witnessing) │ │
│ │ - 多个独立的见证者(Witness)保存树头快照 │ │
│ │ - 任何不一致都会被检测到 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 层级 4: 监控器 (Monitor) │ │
│ │ - 独立监控器持续监控日志状态 │ │
│ │ - 检测任何回滚或篡改 │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.5.2 攻击场景分析
| 攻击类型 | 可行性 | 原因 |
|---|---|---|
| 修改历史条目 | ❌ 不可行 | 根哈希会变化,签名会失效 |
| 删除条目 | ❌ 不可行 | 树大小会减少,验证会失败 |
| 插入虚假条目 | ⚠️ 部分可行 | 条目写入后无法删除,但可被标记为无效 |
| 篡改数据库 | ❌ 不可行 | 树头签名独立于数据库 |
| 替换整个日志 | ⚠️ 理论可行 | 需要所有见证者和监控器同时被攻击 |
| DDoS 攻击 | ⚠️ 可行 | 可通过私有部署和多实例缓解 |
4.5.3 Signed Tree Head (STH)
树头(Tree Head)是 Merkle Tree 当前状态的快照,由 Trillian Log Signer 进行数字签名:
{
"timestamp": 1704067200000,
"tree_size": 534218392,
"root_hash": "4b8e2b5a45e3...",
"signature": {
"algorithm": "ECDSA",
"value": "MEUCIQDx..."
}
}
STH 的安全保证:
- 根哈希绑定了所有历史数据
- 数字签名保证 STH 未被篡改
- 时间戳证明 STH 的生成时间
4.6 读写路径分析
4.6.1 写入路径
客户端 ──► rekor-server ──► 验证条目
│
▼
计算条目哈希
│
▼
Trillian: QueueLeaf
│
▼
写入 Unsequenced 表
│
▼
Sequencer 批量读取
│
▼
更新 Merkle Tree (Subtree 表)
│
▼
计算新的根哈希
│
▼
Log Signer 签名树头
│
▼
写入 TreeHead 表
4.6.2 读取路径
客户端 ──► rekor-server ──► 读取叶子数据
│
▼
从 LeafData 表获取
│
▼
构建包含证明
│
▼
获取当前树头
│
▼
返回条目 + 包含证明
4.6.3 性能特征
| 操作 | 时间复杂度 | 典型延迟 |
|---|---|---|
| 写入条目 | O(log n) | 50-200ms |
| 读取条目 | O(1) | 10-50ms |
| 包含证明 | O(log n) | 20-100ms |
| 一致性证明 | O(log n) | 20-100ms |
4.7 数据流详解
4.7.1 完整数据流
┌──────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────┐
│ 客户端 │ │ rekor-server │ │ Trillian │ │ Database │
│ │ │ │ │ │ │ │
│ 签名数据 │────►│ 解析 + 验证 │────►│ QueueLeaf │────►│ 存储 │
│ │ │ │ │ │ │ │
│ │ │ 计算哈希 │ │ Sequencer │ │ 更新树 │
│ │ │ │ │ 批量合并 │◄───►│ │
│ │ │ │ │ │ │ │
│ │ │ 获取包含证明 │◄────│ GetEntryAnd│ │ 查询 │
│ │ │ │ │ Inclusion │◄────│ │
│ │◄────│ 返回响应 │ │ Proof │ │ │
└──────────┘ └──────────────┘ └────────────┘ └──────────┘
4.7.2 条目生命周期
| 阶段 | 说明 | 存储位置 |
|---|---|---|
| 提交 | 客户端发送条目到 rekor-server | 内存缓冲区 |
| 排队 | 条目被提交到 Trillian | Unsequenced 表 |
| 排序 | Sequencer 分配序号 | 内存 |
| 合并 | 条目被合并到 Merkle Tree | Subtree + LeafData 表 |
| 签名 | 新的树头被签名 | TreeHead 表 |
| 返回 | 包含证明返回给客户端 | — |
4.8 扩展性设计
4.8.1 水平扩展
Rekor 支持通过以下方式扩展:
┌────────────────────────────────────────────────────────┐
│ 负载均衡器 │
└───────────────┬────────────┬───────────────────────────┘
│ │
┌───────▼───┐ ┌──────▼────┐ ┌────────────┐
│rekor- │ │rekor- │ │rekor- │
│server-01 │ │server-02 │ │server-03 │
└───────┬───┘ └──────┬────┘ └──────┬─────┘
│ │ │
└────────────┼─────────────┘
│
┌──────▼──────┐
│ Trillian │ (单主实例)
│ Cluster │
└──────┬──────┘
│
┌──────▼──────┐
│ Database │ (主从复制)
│ Cluster │
└─────────────┘
4.8.2 读写分离
- 读操作:可以水平扩展到多个 rekor-server 实例
- 写操作:通过 Trillian 的 Sequencer 串行化,保证顺序一致性
- 查询操作:通过索引数据库加速
4.9 与 Certificate Transparency 的对比
| 方面 | Certificate Transparency (CT) | Rekor |
|---|---|---|
| 目的 | 审计 SSL/TLS 证书 | 审计软件签名和构件 |
| 数据类型 | X.509 证书 | 多种签名格式 |
| 后端 | Google Trillian | Google Trillian |
| 标准化 | RFC 6962 | Sigstore 规范 |
| 验证者 | 浏览器 / CT Monitor | cosign / 自定义客户端 |
| 公开程度 | 完全公开 | 完全公开(公共实例) |
| 包含证明 | SCT (Signed Certificate Timestamp) | 包含证明(Inclusion Proof) |
4.10 注意事项
单点故障:公共 Rekor 实例由 Sigstore 项目维护,对于高可用场景建议同时使用多个日志实例或部署私有实例。
Sequencer 延迟:Trillian 的 Sequencer 是批量处理的,条目从提交到实际包含在树中可能有几秒延迟。
存储增长:随着条目数量增长,数据库和 Merkle Tree 节点会持续增加。公共实例已有数十亿条目。
密码学更新:如果 SHA-256 被认为不再安全,需要迁移整个日志。目前 SHA-256 仍被认为安全。
4.11 本章小结
| 概念 | 说明 |
|---|---|
| Merkle Tree | 哈希二叉树,提供 O(log n) 的包含证明 |
| Trillian | Google 的透明日志框架,Rekor 的后端 |
| Sequencer | 批量将新叶子合并到树中 |
| Signed Tree Head | 对树头的数字签名,防篡改保证 |
| 包含证明 | 从叶子到根的路径上兄弟节点哈希 |
| 一致性证明 | 证明两个树状态的兼容性 |
| 不可篡改 | Merkle Tree + 签名树头 + 公开见证 |
扩展阅读
- Trillian 透明日志框架
- RFC 6962 - Certificate Transparency
- Merkle Tree 论文 (Ralph Merkle, 1987)
- Transparency Log 设计 (Adam Langley)
- Google’s Certificate Transparency Log
下一章:05 - 签名集成 — 学习如何使用 cosign 和 Sigstore 进行签名集成。