Caloogy 文章
English

哈希链审计日志:让日志没法被悄悄删改

日期
2026 年 9 月 8 日
系列
基础设施
阅读
4 分钟
主题
Audit logHash chainTamper evidentCertificate transparency

一句话版本

入侵者得手后的第一件事,往往是删日志、改日志来掩盖痕迹。哈希链让这件事变得"做得到,但一定会被发现":每条记录都包含上一条记录的哈希,改动任何一条,它后面所有记录的哈希都会对不上。 再把最新的哈希定期保存到一个攻击者碰不到的地方("锚点"),就连"把整条链重新算一遍"也会被识破。区块链、Git、证书透明度日志,都是这个思想。

全文分三层:

  1. 通俗理解:环环相扣的账本
  2. 实际用处:证书透明度、Git、区块链、金融审计
  3. 原理与工程要点:构造、手算例子、外部锚点、Merkle 树

第一层:通俗理解

1.1 环环相扣的账本

想象一本账,每一页的页脚都写着"上一页内容的指纹"。

改一页,就得改后面所有页。

1.2 只改得动"自己的副本"

如果你每天把账本最后一页的指纹抄一份,锁进家里的抽屉(攻击者碰不到的地方),那么攻击者就算把整本账重写一遍,最后一页的指纹也和你抽屉里的对不上。

这就是防篡改(tamper-evident):不是让篡改变得不可能,而是让篡改一定会被发现。


第二层:实际用处

系统 怎么用
证书透明度(Certificate Transparency) 2011 年荷兰 CA DigiNotar 被入侵,攻击者签发了 google.com 的假证书。之后 Google 推动所有 CA 签发的证书都必须写入公开的、只能追加的日志(RFC 6962);浏览器要求证书附带"已入日志"的证明。任何 CA 偷偷签发的证书都会暴露
Git 每个提交的 ID 是对"内容 + 父提交 ID"的哈希。改历史中的任何一个提交,后面所有提交的 ID 都会变,所有人都能看出来
区块链 每个区块头包含上一个区块的哈希,再加上工作量证明或共识,篡改的成本极高
金融与合规审计 银行、交易所的操作日志要求"不可抵赖、不可篡改",哈希链加外部存证是常见实现
密钥透明 WhatsApp、iMessage 把用户公钥写入可审计日志,防止服务器偷偷换钥匙(见《TOFU与公钥钉扎》)

Caloogy Wallet 也采用了哈希链审计日志。


第三层:原理与工程要点

3.1 构造

第 ii 条记录存储:

hi=SHA256(hi−1 ∥ 序号 ∥ 时间 ∥ 操作者 ∥ 内容),h0=0⋯0.h_i = \mathrm{SHA256}\big(h_{i-1} \,\|\, \text{序号} \,\|\, \text{时间} \,\|\, \text{操作者} \,\|\, \text{内容}\big),\qquad h_0 = 0\cdots0.

验证时从头重算一遍,每一条都必须和存储的 hih_i 一致。

3.2 手算例子

三条记录(为简洁,公式取 hi=SHA256(hi−1 ∥ "|" ∥ 事件)h_i = \mathrm{SHA256}(h_{i-1} \,\|\, \texttt{"|"} \,\|\, \text{事件}),只显示前 8 位)

# 事件 上一条哈希 本条哈希
1 登录 alice 00000000 a28ed04e
2 绑定地址 0xAB.. a28ed04e 98992e28
3 修改通知设置 98992e28 8fe3ea53

攻击者把第 2 条改成"绑定地址 0xCD..":

# 事件 上一条哈希 重算出的哈希
1 登录 alice 00000000 a28ed04e ✓
2 绑定地址 0xCD.. a28ed04e 6ea0ec94 ✗(存的是 98992e28)
3 修改通知设置 6ea0ec94 3ab27dfe ✗(存的是 8fe3ea53)

从第 2 条起全部对不上。即使攻击者把存储的哈希也一起改成新值,最后一条变成 3ab27dfe,而外部锚点里记的是 8fe3ea53,照样露馅。(已用 Python 验算。)

3.3 外部锚点:最关键的一步

只在同一个数据库里做哈希链,能防"不懂行的篡改",防不了拿到数据库完全控制权的攻击者:他可以从篡改点开始把后面所有哈希重算一遍。所以必须定期把最新哈希保存到独立的地方:

锚点位置 说明
另一台机器 / 个人电脑 由一个独立程序每天拉取全部日志,用自己的代码重算哈希链,并和以前存的锚点比对
发邮件给自己 简单有效,邮件服务商是独立的第三方
写进公链 最强,但有成本
公开发布 证书透明度的做法:日志对所有人公开,大家都能审计

注意"用自己的代码重算":如果验证程序也跑在被攻破的服务器上,攻击者可以连验证程序一起改。

3.4 数据库层面的加固

3.5 Merkle 树:大规模时的升级版

哈希链要验证第 nn 条,得从头算到第 nn 条。Merkle 树把记录两两哈希、层层向上,最后得到一个根哈希。证明"某条记录在日志里"只需要 log⁡2n\log_2 n 个哈希值。证书透明度、比特币区块里的交易、以太坊的状态,都使用 Merkle 树。

3.6 安全性来自哪里

攻击者想改一条记录又不被发现,就得找到另一份内容,使它的哈希恰好等于原来的值,也就是破解哈希函数的第二原像。对 SHA-256,这需要约 22562^{256} 次尝试(见《生日悖论与哈希碰撞》)。


总结

层次 要点
直觉 每页记上一页的指纹,改一页就得改后面所有页
关键 外部锚点 + 独立代码验证,防止"整条重算"
应用 证书透明度、Git、区块链、审计日志、密钥透明
加固 只追加(触发器禁止改删);不记敏感数据;规模大时用 Merkle 树