百度网盘的”秒传”、Git 的 commit、Dropbox 的同步、Docker 镜像的 layer 缓存——背后都是同一件事:用文件内容的 hash 当地址,相同 hash 视作相同内容。理解这套机制要回答三个问题:用哪种 hash?整文件还是分块?碰撞概率到底多大?
秒传到底”秒”什么
传统上传流程:
本地文件 ──读取 1GB──→ 网络 ──上传 1GB──→ 服务器存盘
(受带宽限制,几分钟到几小时)
秒传流程:
本地文件 ──读取 1GB──→ 算 hash ──上传 32 字节──→ 服务器查表
↓
命中 → link 现有数据 (秒级)
未命中 → 退化回真上传
“秒传”一词有误导性——它没真正传完整文件,传的是文件指纹。慢硬盘的瓶颈反而不是带宽是 I/O——你得本地完整读一遍 1GB 才能算出 hash。SSD 上 BLAKE3 能跑到 6GB/s,机械硬盘读取速度上限就是 hash 速度。
百度网盘的真实秒传指纹
公开资料和抓包分析得到的秒传指纹:
file_size (文件总字节数)
slice_md5 (前 256KB 的 MD5,快速预筛)
content_md5 (整文件 MD5)
content_crc32 (整文件 CRC32,校验完整性)
为什么不是单一 hash:
- slice_md5 算得快,先用它做服务器侧第一轮筛——256KB 的 MD5 < 10ms,能过滤掉 99.9% 的不命中
- content_md5 才是真实命中判定,但要客户端完整读一遍文件
- file_size 双重保险——同 MD5 不同 size 几乎不可能(攻击者要同时碰撞 MD5 + 控制 size)
- content_crc32 是后续传输纠错,不参与去重 key
这套设计的安全模型:抗自然碰撞 + 弱抗主动碰撞。MD5 已经被构造碰撞攻破(2004 年王小云论文),但攻击者要同时让 size 一致、前 256KB 内容一致、整文件 MD5 一致,难度抬高了几个量级——但不是不可能。
整文件 hash vs 分块 hash
整文件 hash 简单但脆——改一个比特,完全不同:
file_v1.bin → SHA-256: a3f2c8...
file_v2.bin → SHA-256: 9b1e4d... (改一字节)
雪崩效应是设计目标——hash 函数的核心安全性质就是输入微变 → 输出剧变。对完整性校验是好事,对增量去重是灾难。
分块 hash 解决”小修改重传”问题:
file (12 MB) → Block 0 (4MB) → SHA-256: a1...
Block 1 (4MB) → SHA-256: b2...
Block 2 (4MB) → SHA-256: c3...
修改第 5MB 处的一个字节 → 只有 Block 1 变化
→ 上传只发 Block 1(4MB)
→ 服务器替换该块
Merkle 树把分块 hash 再 hash 一层,得到根 hash 作为整体指纹:
Root = SHA-256(H_AB || H_CD)
/ \
H_AB = SHA-256(A||B) H_CD = SHA-256(C||D)
/ \ / \
A B C D (4MB blocks)
Git、IPFS、BitTorrent、ZFS、btrfs 全部用 Merkle 树。改 1 个块只用更新该路径上的 log₂(N) 个节点。
固定分块的”插入 1 字节”问题
固定 4MB 切块对”在中间插入”完全失败:
原文件:[Block 0][Block 1][Block 2][Block 3]
↓ 在中间插入 1 字节
新文件:[Block 0'][Block 1'][Block 2'][Block 3']
Block 0 之后所有块全部向后偏移 1 字节,hash 全部改变——明明只改了 1 字节,要重传从插入点到文件尾的所有内容。
内容定义切块 (CDC) 是 rsync 的灵魂
Content-Defined Chunking 用滚动 hash 找”自然边界”——比如 hash 末尾连续 13 个 0 bit 就切一刀。这样:
- 在文件中间插入 1 字节 → 只影响当前块
- 后面的块边界仍由”内容找边界”决定,不受偏移影响
- 平均块大小 = 2^(0 的位数),13 位 0 → 平均 8KB
历史上的 CDC 算法:
| 算法 | 年代 | 用在哪 |
|---|---|---|
| Rabin fingerprint | 1981 | rsync (1996), LBFS, Plan 9 |
| Buzhash | — | bup (Git-like backup) |
| Gear | 2014 | restic (~2015 起) |
| FastCDC | 2016 | borgbackup, restic 新版 |
FastCDC 比 Rabin 快 3×、比 Gear 快 1.5×——已成为现代去重备份系统的事实标准。
实战工具:
- rsync —— 经典文件同步,Rabin 滚动 hash,1996 年就有
- restic / borgbackup —— 加密增量备份,FastCDC + BLAKE2/SHA-256
- Dropbox —— 块级增量同步,公开论文里写了 4MB 固定块(早期)→ 后来改 CDC
- ZFS / btrfs —— 文件系统级去重,块级
- Docker image layers —— layer 级(粗粒度),每个 layer 是 tar.gz 的 SHA-256
碰撞概率的真实数字
生日悖论:N 位 hash,期望第一次碰撞在约 √(π/2 × 2^N) ≈ 1.25 × 2^(N/2) 次:
| hash 位数 | 期望碰撞所需文件数 | 类比 |
|---|---|---|
| 32 (CRC32) | ~ 8 万 | 一个家庭照片库就撞 |
| 64 | ~ 50 亿 | Google 全球用户数级别 |
| 128 (MD5) | ~ 2.2 × 10¹⁹ | 全人类一辈子产文件量级别 |
| 160 (SHA-1) | ~ 1.5 × 10²⁴ | 沙子级别 |
| 256 (SHA-256/BLAKE3) | ~ 4 × 10³⁸ | 宇宙原子数级别 |
关键观察:
- CRC32 绝不能做去重 key——10 万张图就期望撞一次
- MD5 在非对抗场景仍可用——自家网盘库 PB 级也不会自然碰撞
- SHA-256 已经远超物理需求——选它纯粹是为了抗”人为构造”
人为构造碰撞和自然碰撞完全是两件事。MD5 已被工程化攻破——王小云 2004 / Stevens 2009 给出秒级生成”两个 MD5 相同的不同文件”的算法。但秒传场景里这个攻击需要攻击者在受害者之前上传,门槛实际较高。SHA-1 在 2017 年被 Google SHAttered 攻击破掉,2020 年成本降到 $11k——生产环境从 2020 年起一律应迁 SHA-256。
加密 hash vs 非加密 hash
加密 hash(SHA-256、BLAKE3、MD5)的设计目标是抗主动构造——在没有密钥的情况下,攻击者无法在合理算力内找到碰撞或原像。
非加密 hash(xxHash、Murmur、CityHash、SipHash)只保证均匀分布——做哈希表的桶分配、Bloom filter 的指纹够用,但攻击者能秒级构造碰撞。它们的速度优势:
| hash | 速度(单核) | 类型 |
|---|---|---|
| MD5 | 600 MB/s | 加密(已破) |
| SHA-1 | 800 MB/s | 加密(已破) |
| SHA-256 | 400 MB/s | 加密 |
| BLAKE3 | 6,800 MB/s | 加密(最快的) |
| xxHash | 30,000 MB/s | 非加密 |
| Murmur3 | 5,000 MB/s | 非加密 |
BLAKE3 是过去十年最大的工程进步——树形结构天然支持并行(多核可线性加速),速度还把 SHA-256 甩开 10×,安全性等同 SHA-256。Cargo(Rust)、IPFS、syncthing 都已切到 BLAKE3。
选型决策树
你需要 hash 来做什么?
├─ 文件完整性校验(信任源) → SHA-256 或 BLAKE3
├─ 自家备份/网盘去重 → BLAKE3(速度优先)
├─ 公开网盘"秒传"防恶意构造 → SHA-256 + 文件大小双校验
├─ Git 内容寻址 → 跟 Git 走(默认 SHA-1,已开始迁 SHA-256)
├─ rsync 类增量同步 → FastCDC + BLAKE3
├─ 哈希表桶分配(无外部输入) → xxHash(最快)
├─ 哈希表桶分配(有外部输入,防 DoS)→ SipHash(HashDoS 防御)
├─ Bloom filter / LRU 缓存 key → xxHash 或 Murmur
└─ 密码哈希(用户密码) → 不是 hash 范畴!用 Argon2/bcrypt/scrypt
最后一条特别要警告:密码存储绝对不能用 SHA-256 这类——它们设计目标是”快”,密码哈希要的是”慢”(让暴力破解不可行)。Argon2id 是当前推荐,bcrypt 仍可接受,scrypt 已淘汰。
一句话总结
文件秒传 = 传 hash 不传文件;分块去重要用 CDC 不要用固定切块;自家场景 MD5 还能撑、对抗场景一律 SHA-256 / BLAKE3、密码场景用 Argon2id 不用任何普通 hash。