判断一张照片是相机拍的还是模型画的,正在变成一个工程问题而非哲学问题。5 月 19 日 OpenAI 宣布了两项动作——正式加入 C2PA 内容来源标准,并与 Google 合作在生成图像中嵌入 SynthID 隐形水印,同时上线公开验证工具。这不是公关声明,而是给开发者提供了一条可编程的溯源链路。
C2PA:给图像装一份"出生证明"
C2PA(Coalition for Content Provenance and Authenticity)由 Adobe、Microsoft、BBC 等联合发起,核心思路并不复杂:在图像文件内嵌一段签名的 JSON manifest,记录这张图从拍摄到编辑再到发布的每一步操作。
manifest 的结构大致如下:
- claim:声明动作类型(创建、编辑、导出等)、使用的软件、时间戳
- signature:用 X.509 证书对 claim 做数字签名,防止篡改
- thumbnail:可选的缩略图哈希,用于绑定 manifest 与像素内容
- ingredient list:如果图像由多张素材合成,每张素材的 manifest 也会被引用
这些数据被封装成 JUMBF(JPEG Universal Metadata Box)格式,直接嵌入 JPEG/PNG 的二进制容器中。对终端用户来说,图像外观不变;对验证工具来说,只要解析 JUMBF box 就能拿到完整链路。
OpenAI 加入 C2PA 的意义在于:DALL·E 和 GPT-4o 生成的图像出厂就自带 manifest,声明"这张图由 OpenAI 模型生成",并附带 OpenAI 的签名证书。下游任何支持 C2PA 的平台——社交媒体、新闻编辑系统、取证工具——都可以自动读取并展示这段信息。
SynthID:像素级的隐形水印
C2PA 解决的是"谁生成了这张图"的声明问题,但 manifest 是明文元数据,裁剪、压缩或刻意剥离都可能让它消失。SynthID 补的是另一层:把水印直接写进像素。
SynthID 由 Google DeepMind 开发,技术路线是:
- 在生成图像的扩散模型中,向噪声空间注入一组经过训练的不可见信号
- 这些信号对人眼几乎无感知差异,但可以被专用检测器从像素统计中提取
- 水印对常见攻击——裁剪、缩放、颜色调整、JPEG 压缩——保持鲁棒
与 C2PA manifest 不同,SynthID 不依赖文件容器,不存储在 EXIF 或 JUMBF 中。即使有人把元数据全部擦除,只要像素没有被严重破坏,检测器仍然能判定"这张图大概率由 AI 生成"。
OpenAI 与 Google 合作的部署方式是:DALL·E 生成图像时同时嵌入 C2PA manifest 和 SynthID 水印,形成双重保险——明文链路可读,隐形水印可查。
实战:读取与验证 C2PA 元数据
下面给出两种可直接运行的方式,从一张图像中提取 C2PA manifest。
方式一:用 exiftool 快速查看
exiftool 从 12.54 版本起支持读取 JUMBF/C2PA box。安装后一行命令即可:
# macOS
brew install exiftool
# 对一张 DALL·E 生成的图像提取 C2PA 信息
exiftool -jumbf -json output_from_dalle.png
输出会包含 manifest 的 JSON 结构,你可以看到 assertions 数组里声明了生成工具、动作类型和时间戳。如果图像被篡改或 manifest 被删除,这条命令将返回空结果。
方式二:用 Python 解析并验证签名
对于需要集成到后端服务的场景,可以用 c2pa-python 库做更严格的验证:
pip install c2pa-python
from c2pa import C2paReader, C2paVerifier
# 1. 读取 manifest
reader = C2paReader("output_from_dalle.png")
manifest_store = reader.read()
# 打印所有 claim
for manifest in manifest_store.manifests:
print(f"Manifest ID: {manifest.id}")
for assertion in manifest.assertions:
print(f" - {assertion.label}: {assertion.data}")
# 2. 验证签名是否有效
verifier = C2paVerifier()
result = verifier.verify("output_from_dalle.png")
print(f"签名验证结果: {result.is_valid}")
print(f"签发者证书: {result.signer_certificate.subject}")
print(f"验证时间戳: {result.validation_timestamp}")
运行前需要准备一张包含 C2PA manifest 的图像(目前 DALL·E 新生成的图像已默认嵌入)。如果验证通过,result.is_valid 为 True,你可以确信这张图的来源声明未被篡改。
注意:
c2pa-python的 API 可能随版本变化,上述代码基于 0.5.x 版本。如果接口有差异,请查阅 c2pa-python GitHub 的最新文档。
方式三:给自己的生成服务添加 C2PA manifest
如果你在自建图像生成服务,也可以主动嵌入 C2PA 声明,让下游平台识别你的输出:
from c2pa import C2paCreator, Claim, Assertion
# 定义生成动作的 assertion
gen_assertion = Assertion(
label="c2pa.actions.generative",
data={
"action": "created",
"softwareAgent": "my-ai-image-service v1.0",
"parameters": {"model": "stable-diffusion-xl", "prompt": "a cat on Mars"},
},
)
# 构建 claim 并签名(需要你自己的 X.509 私钥证书)
claim = Claim(assertions=[gen_assertion])
creator = C2paCreator(
claim=claim,
signer_cert_path="my_sign_cert.pem",
signer_key_path="my_sign_key.pem",
)
creator.embed("generated_cat_on_mars.png", "output_with_manifest.png")
这样输出的 PNG 就携带了你的签名声明,任何 C2PA 验证器都能追溯到你的服务。
双层溯源的现实边界
C2PA + SynthID 的组合看起来覆盖了明文和隐形两条链路,但落地时仍有几个边界需要正视:
| 场景 | C2PA manifest | SynthID 水印 | 说明 |
|---|---|---|---|
| 正常分享(未修改) | ✅ 可读可验证 | ✅ 可检测 | 双层完整 |
| 元数据被刻意擦除 | ❌ 丢失 | ✅ 仍可检测 | SynthID 兜底 |
| 大幅裁剪 + 重压缩 | ❌ 可能丢失 | ⚠️ 棒性下降 | SynthID 对极端变换有阈值 |
| 截屏后重新拍照 | ❌ 丢失 | ⚠️ 检测概率降低 | "二次采集"是当前最难场景 |
| 非 OpenAI/Google 模型生成 | ❌ 无 manifest | ❌ 无 SynthID | 标准覆盖取决于行业采纳速度 |
截屏重拍这条攻击路径,目前没有任何技术方案能完美应对。C2PA 和 SynthID 的设计目标是覆盖"正常传播链路中的篡改",而非对抗专业取证对抗者。
落地建议
如果你是平台方或工具开发者,以下清单可以作为起步参考:
- 读取侧:在内容审核流水线中加入 C2PA manifest 解析步骤,优先使用
c2pa-python或exiftool,对有 manifest 的图像自动标注来源 - 检测侧:对 manifest 缺失的图像,调用 SynthID 检测 API(Google 已公开检测接口)做二次判断
- 写入侧:自建生成服务时,申请 X.509 证书并使用
C2paCreator嵌入 manifest,不要等平台替你做 - 展示侧:在用户界面中把 C2PA 声明翻译成可读标签,例如"AI 生成 · OpenAI DALL·E · 2025-05-19",而非只显示一个晦涩的图标
- 监控侧:记录 manifest 验证失败的图像比例,作为平台内容健康度的指标
C2PA 是一个开放标准,SynthID 是一个可扩展的水印框架。它们各自不完美,但叠加后把"这张图从哪来"从一个猜测游戏变成了一个可验证的工程流程。接下来的关键变量是行业采纳速度——当主流生成工具都出厂自带 manifest 和水印时,溯源才不再是少数人的特权。