C2PA 照片签名被攻破:把整个文件排除出哈希,时间戳照样有效

一张彩票的照片,带完整的 C2PA 内容签名和受信任时间戳,显示它在开奖前几小时就拍下了「中奖号码」。验证工具显示签名有效、时间戳有效、照片未篡改。唯一的破绽:号码是开奖之后 P 上去的。这不是假设,是安全研究者 David Buchanan(网名 retr0id)10 月 2 日发布的演示,也是目前对「照片溯源标准」最直接的一次证伪。Lobsters 上这个话题拿到 20 分不算爆,但它指向的问题值得展开:当「这张照片是真的」变成一门生意,标准里的一行宽容条款就能让整条信任链空转。

C2PA 官方宣传图:Content Credentials 声明照片的拍摄设备与时间,这正是这次被绕过的承诺

C2PA 是什么,两段签名各自在证明什么

C2PA(Content provenance and authenticity,内容溯源与真实性规范)是 Adobe 发起、微软谷歌等大厂参与的内容凭证标准,中文名常译作「内容凭证」。它的核心做法是在图片、视频的文件里嵌入一份签名过的元数据(manifest),声明这段内容的来源:什么设备拍的、什么软件处理的、何时何地。Adobe 的 Content Credentials、谷歌的 SynthID 之外的「真图标记」路线,都是这套标准的应用。

一份典型的 C2PA manifest 带两段签名,各管一件事:

第一段是 claim signature(声明签名)。相机应用用它声明「这是本机拍摄的照片,拍摄于这些坐标、这个时间」。Buchanan 在 8 月的前一篇论文里已经演示过这类签名形同虚设:在 Google Pixel 相机应用这类「旗舰级 C2PA 实现」上,任何人都能签下任意的声明内容。声明是人写的,签名只证明「有人签过这句话」,不证明这句话为真。

第二段才是这次的主角:时间戳签名。设备自己报的时间不可信,所以 C2PA 引入了时间戳机构(TSA,Time Stamp Authority):设备把文件哈希发给 TSA 服务器,TSA 用 RFC 3161 协议回签一句「我在这个时刻见过这个哈希」。只要 TSA 不作恶,这段签名就证明了「被签的数据在某个时刻之前已经存在」。它是整个 C2PA 时间证明的锚点,也是多数人眼里最难绕过的一环。

Buchanan 这次没有攻击 TSA。他明说了:假设 TSA 完全按规范工作。他找的是规范本身的漏洞,他称之为自己最喜欢的一类 bug:spec footgun(规范自残)。

攻击:把整个文件从签名里排除掉

C2PA 规范里有一个名为 exclusions(排除区)的机制:签名计算时可以指定文件的某些字节范围不参与哈希。设计初衷是处理格式上的死结,比如 PNG 文件每个数据块都带 CRC32 校验和,签名嵌入文件后校验和本身要更新,不排除它,签名就永远自相矛盾。

问题在于规范对排除区的大小没有任何约束。Buchanan 构造的 manifest 把排除区设为整个文件:

text
"c2pa.hash.data" : {
  "exclusions" : [
    { "start" : 0, "length" : 3995383 }
  ],
  "name" : "jumbf manifest",
  "alg" : "sha256",
  "hash" : "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
}

3995383 正是整个文件的长度,而那个哈希值是空字符串的 SHA-256(可以用 openssl sha256 -binary /dev/null 验证)。签名链完整无缺:声明签名的对象是一个空哈希,时间戳签名覆盖声明签名,TSA 忠实地证明「在 8 月 28 日开奖前,确实存在一个空字符串的哈希」。整套密码学机制运转完美,同时什么也没有证明。文件随后被整体修改(把彩票号码 P 成中奖号码),所有签名依然有效。

Buchanan 用官方验证工具 verify.contentauthenticity.org 校验了这份文件:没有任何异常告警。开奖号码可以在 EuroMillions 官网核对,确实是开奖后修改的。

为什么修起来比看起来难

最容易想到的修复是「排除整个文件就报错」。Buchanan 在文中直接否掉了这个方案的充分性:如果只排除文件的一小部分呢?排除区里的字节可以随意改动而不被发现,哪怕只排除一段,只要那段恰好承载画面内容,攻击照样成立。MD5 碰撞攻击的历史已经展示过「只差几个字节、视觉效果天差地别」的构造手法。验证器没有能力判断某个排除区是否「无害」,因为「无害」取决于语义,而哈希验证是纯语法操作。

排除区又不能简单删掉。PNG 的 CRC32 死结真实存在,没有排除机制,C2PA 就无法支持最常用的无损图片格式。Buchanan 给出的方向是把排除规则写死:规范按文件格式逐一枚举「哪些区域允许排除」(PNG 的 CRC32 字段、JPEG 的注释段等),验证器强制核对排除区是否越界。这等于给每个支持格式的文件结构写一份白名单,工作量在规范侧和各家实现侧都不小,而 C2PA 规范 2.4 刚刚发布,实现矩阵还在扩张。

时间线上还有一层背景:Neal Krawetz(hackerfactor 博主)在 2025 年 6 月的 C2PA 缺陷清单里已经点名「大排除区」问题——当时指出的是实现中排除区往往很大、客观上留出篡改面。Buchanan 的新演示把问题推进了一格:恶意签名者可以主动利用这个机制,而且现有全部验证工具都不告警。一个安全研究者 2025 年就写进清单的缺陷,到 2026 年 10 月仍能做出完整 PoC,这正是标准类漏洞的常见节奏:发现问题容易,推动规范收紧慢。

「拍到的」和「没被改过的」是两个承诺

把这次演示放回 C2PA 的定位里看,它的实际杀伤范围需要校准。

C2PA 的宣传场景是 AI 生成内容泛滥后的「真图凭证」:新闻机构、相机制造商给照片签上来源声明,对抗「眼见为实」的失效。但这次演示说明,即便忽略「声明可以乱签」这一层(前作已证),连「这张图自时间 T 以来未被修改」这个更弱、更技术性的承诺,在当前规范下也无法兑现。两个承诺叠起来,C2PA 目前能提供的信息量接近于零,验证工具给出的绿色对勾只是「签名链格式正确」。

这不意味着内容凭证路线整体无用。TSA 时间戳机制本身工作正常,问题出在它被签的对象可以为空。收紧排除区白名单之后,「未被修改」的承诺可以变得可兑现,剩下「拍摄声明可伪造」那一层需要硬件级证明(安全元件签拍摄事件),这确实是谷歌 Pixel 10 「设备端可信时间戳」这类功能试图走的方向,只是 Buchanan 对设备自证时间的路线同样持怀疑态度,尚未测评。

对普通读者的操作级结论反而简单:在规范收紧之前,任何「带内容凭证所以是真的」的说法都不构成证据,绿色对勾等于零。真正的验证依然是老三样:原始出处、多信源交叉、发布者信誉。

参考链接