一句话版本
入侵者得手后的第一件事,往往是删日志、改日志来掩盖痕迹。哈希链让这件事变得"做得到,但一定会被发现":每条记录都包含上一条记录的哈希,改动任何一条,它后面所有记录的哈希都会对不上。 再把最新的哈希定期保存到一个攻击者碰不到的地方("锚点"),就连"把整条链重新算一遍"也会被识破。区块链、Git、证书透明度日志,都是这个思想。
全文分三层:
- 通俗理解:环环相扣的账本
- 实际用处:证书透明度、Git、区块链、金融审计
- 原理与工程要点:构造、手算例子、外部锚点、Merkle 树
第一层:通俗理解
1.1 环环相扣的账本
想象一本账,每一页的页脚都写着"上一页内容的指纹"。
- 有人想偷偷改第 5 页:改完之后,第 5 页的指纹变了,和第 6 页页脚记的不一样;
- 他只好接着改第 6 页的页脚,于是第 6 页的指纹也变了,又和第 7 页对不上;
- ……一直改到最后一页。
改一页,就得改后面所有页。
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 构造
第 条记录存储:
验证时从头重算一遍,每一条都必须和存储的 一致。
3.2 手算例子
三条记录(为简洁,公式取 ,只显示前 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 数据库层面的加固
- 数据库触发器禁止对日志表执行
UPDATE和DELETE,只允许INSERT; - 写日志的服务账号没有删除权限;
- 日志里不要存密码、私钥、完整卡号等敏感数据(日志往往比主数据保存得更久、看的人更多)。
3.5 Merkle 树:大规模时的升级版
哈希链要验证第 条,得从头算到第 条。Merkle 树把记录两两哈希、层层向上,最后得到一个根哈希。证明"某条记录在日志里"只需要 个哈希值。证书透明度、比特币区块里的交易、以太坊的状态,都使用 Merkle 树。
3.6 安全性来自哪里
攻击者想改一条记录又不被发现,就得找到另一份内容,使它的哈希恰好等于原来的值,也就是破解哈希函数的第二原像。对 SHA-256,这需要约 次尝试(见《生日悖论与哈希碰撞》)。
总结
| 层次 | 要点 |
|---|---|
| 直觉 | 每页记上一页的指纹,改一页就得改后面所有页 |
| 关键 | 外部锚点 + 独立代码验证,防止"整条重算" |
| 应用 | 证书透明度、Git、区块链、审计日志、密钥透明 |
| 加固 | 只追加(触发器禁止改删);不记敏感数据;规模大时用 Merkle 树 |