上周 Anthropic 正式给 Claude 上线了统计型文本水印,还专门发了篇博客拆解原理。没几天,一个 MIT 协议的开源项目 watermarks-remover 就冲上 11,000 星——Claude 水印、谷歌 SynthID-Text、OpenAI 的溯源标记、文件里的 C2PA 元数据,它都能清。对做 LLM 应用的人来说这事值得看:水印正在变成平台默认行为,而这个开源反击已经把技术的边界暴露得很清楚。
统计型水印的原理:换掉模型那颗「骰子」
不是零宽字符那种老套路。统计水印什么都不改:模型输出的还是正常 token,秘密在「采样」这一步。LLM 每生成一个 token 都是在概率分布上抽样,相当于掷骰子。KGW 系水印(SynthID-Text 和 Claude 水印都属于这一族)把骰子换成确定性的:随机数 = 密钥 + 前文上下文的哈希。Anthropic 自己的比喻是——庄家偷偷把骰子换成了圆周率的小数位。玩家看起来还是在随机走,但庄家拿着密钥随时能回放轨迹,抓出谁用了这副「骰子」。
# KGW 式水印的简化逻辑
def sample(logits, key, prefix_hash):
rng = PRNG(seed=hash(key, prefix_hash)) # 密钥决定的确定性随机源
green = pick_green_list(logits, rng, 0.5) # 约一半词表进「绿名单」
if token in green:
logits[token] += delta # 给绿名单加权
return sample_with_rng(logits, rng)验方拿着密钥重放同一序列,看观察到的词是否偏向绿名单即可。不额外耗 token、没有隐藏字符,轻度修改也洗不掉——因为信号藏在「整体选词统计」里,不在任何一个词上。
为什么会被破:三个攻击面
- Unicode 残留:部分管线还在用物理标记(隐形字符、特殊空白、双向控制符)。正则就能清,而且无损。
- 统计信号:水印活在选词分布里。只要用另一个模型逐句改写,文本就被重新「掷了一次骰子」,密钥对不上了。这是根本弱点:水印只能事后取证,防不了主动改写。
- 文件溯源:PNG/JPEG/SVG/PDF/DOCX 里的 C2PA 清单、EXIF/XMP、文档属性,重写文件即可剥离。
11k 星项目拆解:三层「扒皮」+ 自证闭环
架构刻意保持朴素:一个 Agent skill + 一个纯标准库 Python 服务。skill 只是薄客户端,通过 HTTP 驱动后端,Agent 宿主连 Python 都不用装。核心三层:
- Layer A:确定性文本清洁——隐形 Unicode、特殊空白、bidi、tag 字符,无损清除。
- Layer B:统计攻击——调非源头模型(本地 Ollama 如 llama3.2,或任意 API)按释义 prompt 逐句改写,保住意思但摧毁原采样分布。作者明说会有一定质量损失。
- Layer C:文件级清洗——C2PA/EXIF/XMP/文档属性,靠 c2patool、exiftool、qpdf(PDF 需要 qpdf 结构性重建才是真剥离)。Magic Bytes 检测让文本工具拒绝二进制输入,修掉了旧版把 docx 当文本清、结果损坏文件的 bug。
- 图像像素水印:可选重型后端——CtrlRegen(ICLR 2025,ControlNet + DINOv2 IP-Adapter)不是涂抹而是「受控重生成」一张高度相似的图;MarkDiffusion 做扩散水印的盲重生成攻击。
- 验证闭环:最有工程味的部分——用清华的 MarkLLM 先给文本打水印、再清洗、再检测,直到检测器报「未检出」才宣布成功。
上手:inspect / clean / serve
核心路径只需要 Python 3.10+ 标准库,零依赖、不需要 Docker:
# 起 HTTP 服务
python3 service/scripts/server.py --host 127.0.0.1 --port 8765
# 先检后清,任何支持格式
python3 service/scripts/inspect_file.py draft.md
python3 service/scripts/clean_file.py draft.md -o draft.cleaned.md
python3 service/scripts/clean_file.py photo.png -o photo.cleaned.pngHTTP API 是给 Agent 和 Web 应用用的接口:
WM=http://127.0.0.1:8765
curl -s "$WM/health" # {"ok": true, "version": ...}
curl -s -X POST "$WM/clean" -H 'Content-Type: application/json' \
-d '{"file":"<base64>","name":"notes.md"}'Layer B 默认只打印 prompt、不调模型;真要改写时接 Ollama:
WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2 \
python3 service/scripts/rewrite_text.py draft.md -o draft.rewritten.md完整栈有现成镜像:ghcr.io/guillaumemeyer/watermarks-remover:latest,另有 :markllm-latest 和 :markdiffusion-latest 验证镜像。
给构建方的建议
- 按「可取证」设计,别按「不可破解」设计:统计水印抗轻度编辑,但扛不住逐句改写。合规故事不能只压在这一层。
- 纵深防御:统计水印 + 文件 C2PA + 服务端归因(谁在何时用哪个密钥生成了什么)三层叠加;密钥只留服务端,对外只暴露检测接口。
- 监控逃逸:改写式去水印现在很便宜。定期在自己内容上测检测率,掉点要当信号处理。
- 别过度承诺检测:这仓库的验证闭环恰恰说明——「删掉了」只有被检测器确认才算数。给置信度和方法,别给绝对结论。
一句实话:剥离溯源标记可能违反平台服务条款,部分地区还涉法。只对你自己拥有且合规的内容使用。