AI 生成内容如何证明来源:C2PA、Content Credentials 与签名校验

生成式 AI 把图片、视频和音频的生产成本降到了很低,也把一个老问题推到了前台:看到一份数字内容时,我们怎样知道它从哪里来、经过哪些工具、是否在发布后被替换过?仅靠文件名、网页说明或"AI 生成"水印回答不了这个问题。文件名可以改,网页描述可以复制,肉眼可见的水印可以裁掉,检测模型也只能给出概率,不能重建可靠的生产链。

C2PA(Coalition for Content Provenance and Authenticity)提供了另一条路线。它不试图猜一张图是不是由 AI 生成,而是让生产工具在创建和编辑时写入结构化声明,用内容摘要把声明与资产绑定,再用数字证书对声明签名。验证端由此可以检查:声明是否仍与当前文件匹配、签名是否有效、证书能否连接到认可的信任锚、编辑历史是否存在可验证的前序材料。

本文以截至写作时的正式规范 C2PA 2.4 为依据。2.4 于 2026 年 4 月发布,引入了 Content Credential JSON 派生表示、仓库回执声明等能力,也继续沿用 C2PA 2.x 的 X.509 信任模型。需要先把结论说在前面:C2PA 验证的是来源声明、内容绑定和签名链,不负责判断画面描述的事件是否真实。来源链验证成功,不等于内容事实为真。 一个可信机构完全可能发布错误信息;一张没有内容凭证的照片也不因此必然是假图。C2PA 提供的是可审计证据,不是"真相判定器"。

1. 先区分三个经常混淆的问题

面对一张网络图片,人们常把三个问题揉在一起。第一个问题是"文件有没有在某个时间点之后被改动",它属于完整性校验;第二个问题是"谁或哪个设备、软件对这份声明负责",它属于身份与信任;第三个问题是"图片里的事情是否真的发生",它属于事实核查。三个问题有关联,却不能互相替代。

普通 SHA-256 可以回答完整性问题。发布方把原文件摘要放在可信网站,用户重新计算摘要,二者一致就说明字节没有变化。但摘要本身不携带编辑行为、所用工具和输入材料,也没有统一展示方式。数字签名在摘要之上增加签名者身份:持有私钥的一方签名,验证者使用证书中的公钥校验。然而,如果每家产品自定义签名容器、元数据字段和验证结果,跨平台仍然无法互操作。

C2PA 的价值,是把资产绑定、声明结构、签名封装、证书要求、验证状态和用户体验放进同一套开放规范。它允许相机在拍摄时建立第一份凭证,编辑软件把原图作为 ingredient 写入下一份凭证,发布系统再补充发布动作。消费者最终看到的不是一个孤立标签,而是一条可逐段检查的来源链。

"AI 检测器"处理的是另一个问题。检测器从像素、频谱或语言分布推测生成概率,会受到模型更新、压缩、截图、混合编辑和分布漂移影响。C2PA 则读取生产者主动提供并签名的证据。前者可能发现未声明的生成内容,后者能明确记录已声明的生成过程;它们可以互补,但任何一方都不应被包装成百分之百准确的真假裁判。

2. Content Credential 到底包含什么

Content Credential 可以理解为与数字资产关联的一组可验证来源数据。技术上,资产通常包含一个 Manifest Store,其中可保存多份 C2PA Manifest;被当前资产选中的那一份叫 active manifest。Manifest 内部主要包含 Claim、Assertions、Claim Signature 以及对前序材料的引用。

Claim 是签名覆盖的核心结构。它列出本次声明引用了哪些 assertions、采用什么哈希算法、由什么 claim generator 生成。C2PA 2.4 建议在 claim_generator_info 中声明规范版本,但这个字段只是信息提示,验证器不能因为看到"2.4.0"就跳过实际结构和签名校验。

Assertions 是具体陈述。例如 actions assertion 可以记录创建、打开、调整尺寸或生成式处理等动作;ingredient assertion 可以指向作为输入的原图、音轨或其他媒体;metadata assertion 可以保存规范允许的元数据;缩略图、数据类型和软绑定信息也有各自结构。声明"由训练算法生成"是一条来源陈述,不是对输出质量和事实正确性的担保。

Claim Signature 通常采用 COSE Sign1 结构,由符合规范要求的 X.509 证书和私钥生成。签名保护 Claim,Claim 又通过带哈希的引用保护 Assertions。资产本体通过 hard binding 与 Manifest 关联,所以替换像素、音频帧或受保护字节后,内容绑定校验会失败。这个层层引用的结构避免了"只签元数据却没有签文件"的漏洞。
#mermaid-svg-rtsoxlYskGH9l8HR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rtsoxlYskGH9l8HR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rtsoxlYskGH9l8HR .error-icon{fill:#552222;}#mermaid-svg-rtsoxlYskGH9l8HR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rtsoxlYskGH9l8HR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rtsoxlYskGH9l8HR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rtsoxlYskGH9l8HR .marker.cross{stroke:#333333;}#mermaid-svg-rtsoxlYskGH9l8HR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rtsoxlYskGH9l8HR p{margin:0;}#mermaid-svg-rtsoxlYskGH9l8HR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rtsoxlYskGH9l8HR .cluster-label text{fill:#333;}#mermaid-svg-rtsoxlYskGH9l8HR .cluster-label span{color:#333;}#mermaid-svg-rtsoxlYskGH9l8HR .cluster-label span p{background-color:transparent;}#mermaid-svg-rtsoxlYskGH9l8HR .label text,#mermaid-svg-rtsoxlYskGH9l8HR span{fill:#333;color:#333;}#mermaid-svg-rtsoxlYskGH9l8HR .node rect,#mermaid-svg-rtsoxlYskGH9l8HR .node circle,#mermaid-svg-rtsoxlYskGH9l8HR .node ellipse,#mermaid-svg-rtsoxlYskGH9l8HR .node polygon,#mermaid-svg-rtsoxlYskGH9l8HR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rtsoxlYskGH9l8HR .rough-node .label text,#mermaid-svg-rtsoxlYskGH9l8HR .node .label text,#mermaid-svg-rtsoxlYskGH9l8HR .image-shape .label,#mermaid-svg-rtsoxlYskGH9l8HR .icon-shape .label{text-anchor:middle;}#mermaid-svg-rtsoxlYskGH9l8HR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rtsoxlYskGH9l8HR .rough-node .label,#mermaid-svg-rtsoxlYskGH9l8HR .node .label,#mermaid-svg-rtsoxlYskGH9l8HR .image-shape .label,#mermaid-svg-rtsoxlYskGH9l8HR .icon-shape .label{text-align:center;}#mermaid-svg-rtsoxlYskGH9l8HR .node.clickable{cursor:pointer;}#mermaid-svg-rtsoxlYskGH9l8HR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rtsoxlYskGH9l8HR .arrowheadPath{fill:#333333;}#mermaid-svg-rtsoxlYskGH9l8HR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rtsoxlYskGH9l8HR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rtsoxlYskGH9l8HR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rtsoxlYskGH9l8HR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rtsoxlYskGH9l8HR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rtsoxlYskGH9l8HR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rtsoxlYskGH9l8HR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rtsoxlYskGH9l8HR .cluster text{fill:#333;}#mermaid-svg-rtsoxlYskGH9l8HR .cluster span{color:#333;}#mermaid-svg-rtsoxlYskGH9l8HR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rtsoxlYskGH9l8HR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rtsoxlYskGH9l8HR rect.text{fill:none;stroke-width:0;}#mermaid-svg-rtsoxlYskGH9l8HR .icon-shape,#mermaid-svg-rtsoxlYskGH9l8HR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rtsoxlYskGH9l8HR .icon-shape p,#mermaid-svg-rtsoxlYskGH9l8HR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rtsoxlYskGH9l8HR .icon-shape rect,#mermaid-svg-rtsoxlYskGH9l8HR .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rtsoxlYskGH9l8HR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rtsoxlYskGH9l8HR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rtsoxlYskGH9l8HR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有效且受信
有效但未知签发者
绑定或签名失败
没有凭证
相机或生成模型

创建资产
Claim Generator

组织 Assertions
计算 Hard Binding

绑定资产字节
私钥签署 Claim

附证书链与时间戳
嵌入或外置

C2PA Manifest Store
分发平台保存资产
Manifest Consumer

读取与验证
策略判定
展示来源与编辑历史
展示来源未知
警告可能被篡改
显示无可用来源信息

Manifest 可以嵌入 JPEG、PNG、MP4、PDF 等受支持格式,也可以作为外部资源被引用。嵌入方式便于凭证随文件流转,但某些社交平台会重新编码并删除元数据;外置方式便于集中管理,却依赖网络地址和仓库可用性。C2PA 的 durable Content Credential 通过软绑定增强找回能力,例如使用不可见水印或指纹去仓库查询 Manifest。软绑定适合恢复线索,硬绑定仍承担精确完整性校验,两者不能互换。

3. 签名可信要经过四层判断

开发者最容易犯的错误,是看到"signature valid"就把页面标成"真实"。生产验证至少要区分四层状态。

第一层是 well-formed。验证器能否解析容器、Manifest、Claim、Assertions 和签名结构?格式错误、字段类型错误或无法解析的对象在这一层失败。能够解析只说明语法成立,不说明内容绑定或签名有效。

第二层是 valid manifest 与 valid asset。验证器重新计算 Assertions 哈希、Claim 引用和资产 hard binding,确认摘要匹配,并检查 COSE 签名。如果当前文件被重新压缩、裁切或替换而没有生成新的 Manifest,资产绑定会失败。如果 Claim 被修改,签名会失败。这层回答的是"自签名以来,这份受保护内容和声明是否保持一致"。

第三层是 trusted。有效签名可以来自未知测试证书或企业私有 CA。只有证书链连接到验证器认可的信任锚,并通过用途、有效期、撤销和相关策略检查,验证器才应把它归为受信来源。C2PA 提供 Trust List 和 TSA Trust List,企业也可以为内部工作流配置自己的信任锚。测试证书签出的文件可以完整且签名有效,但不应在公网产品中显示为已知可信签发者。

第四层是 业务可信判断。消费者根据签名者、声明内容、使用场景和其他证据决定是否采信。例如新闻编辑部可能信任合作通讯社的签名,却不接受未知个人证书;保险理赔系统还要核对拍摄时间、设备登记、案件编号和反欺诈信号。C2PA 规范不替业务做这个决定。

因此界面至少要能表达"无凭证""凭证结构损坏""签名或内容绑定失败""签名有效但签发者未知""签名有效且来自受信签发者"。把这些状态压成绿色和红色两个图标,会把未知误报成恶意,也会把签名有效误报成事实真实。

4. 一条完整的签发链怎样建立

设想一个电商团队使用生成模型制作商品背景图。原始商品照片来自摄影棚,背景由模型生成,设计师随后调整色彩并导出。合理的来源链不是在最终 JPEG 上补一句"AI 生成",而是让每个关键环节形成可验证关系。

摄影棚设备先给原图签发 Manifest,记录采集工具和创建动作。生成服务把原图作为 ingredient,并记录生成式处理的 digital source type、使用的软件身份和必要的动作信息。设计工具导入生成结果时验证前序凭证,再在导出时建立新的 Manifest,把前一版本作为 ingredient,记录颜色调整和裁切。发布系统可以保持最后一份凭证不变,也可以基于企业发布证书增加更新 Manifest 或发布声明。

每个环节只陈述自己能够负责的事实。生成服务可以证明"这个服务在此时处理了该输入,并产生与摘要匹配的输出",但不应声称"图中商品百分之百与实物一致";设计工具可以声明做过裁切,却不能替摄影师担保拍摄地点。最小、准确、可追责的声明比一份包罗万象但无法核实的声明更有价值。

签发服务还必须保护私钥。开发环境可以使用 SDK 提供的测试证书,生产环境不应把 PEM 私钥放在代码仓库、容器镜像或共享磁盘。更稳妥的架构是让密钥留在 KMS 或 HSM,业务服务提交待签摘要,由受控签名接口完成加密运算。私钥访问要有工作负载身份、最小权限、速率限制和审计日志;证书轮换不能覆盖旧记录,需要结合可信时间戳维持历史签名的可验证性。

5. 用 Python 创建并读取内容凭证

CAI 开源 SDK 提供 Rust、Python、Node.js 等实现。下面以 Python 3.10 以上环境演示核心流程。安装库:

bash 复制代码
python -m venv .venv
.venv\Scripts\activate
pip install c2pa-python

签名需要符合要求的证书链和私钥。下面代码适合本地实验,certs.pem 和 private_key.pem 应使用官方测试夹具或专门的开发证书,不能拿自签名证书冒充生产可信证书。生产私钥应改由 KMS 或 HSM 托管。

python 复制代码
# sign_asset.py
from __future__ import annotations

import json
from pathlib import Path

from c2pa import (
    Builder,
    C2paSignerInfo,
    C2paSigningAlg,
    Context,
    Reader,
    Signer,
)


def sign_image(source: Path, target: Path, cert: Path, key: Path) -> dict:
    signer_info = C2paSignerInfo(
        alg=C2paSigningAlg.ES256,
        sign_cert=cert.read_bytes(),
        private_key=key.read_bytes(),
        ta_url=b"http://timestamp.digicert.com",
    )
    signer = Signer.from_info(signer_info)
    context = Context.from_dict(
        {
            "verify": {"verify_after_sign": True},
            "builder": {
                "claim_generator_info": {
                    "name": "catalog-image-pipeline",
                    "version": "1.0.0",
                    "specVersion": "2.4.0",
                }
            },
        },
        signer=signer,
    )
    manifest = {
        "title": source.name,
        "format": "image/jpeg",
        "claim_generator_info": [
            {"name": "catalog-image-pipeline", "version": "1.0.0"}
        ],
        "assertions": [
            {
                "label": "c2pa.actions.v2",
                "data": {
                    "actions": [
                        {
                            "action": "c2pa.created",
                            "digitalSourceType": (
                                "http://c2pa.org/digitalsourcetype/"
                                "trainedAlgorithmicMedia"
                            ),
                        }
                    ],
                    "allActionsIncluded": True,
                },
            }
        ],
    }
    builder = Builder(json.dumps(manifest), context=context)
    with source.open("rb") as src, target.open("w+b") as dst:
        builder.sign("image/jpeg", src, dst)

    reader = Reader(str(target), context=context)
    return json.loads(reader.json())


if __name__ == "__main__":
    result = sign_image(
        Path("source.jpg"),
        Path("signed.jpg"),
        Path("certs.pem"),
        Path("private_key.pem"),
    )
    print(json.dumps(result, ensure_ascii=False, indent=2))

这段代码的重点不是某个字段名称,而是"签后立即验"。verify_after_sign 可以尽早发现证书、算法、结构或写入错误;重新用 Reader 打开目标文件,则覆盖了落盘后的真实读取路径。线上还应把源文件摘要、目标文件摘要、证书序列号、签名任务编号和 SDK 版本写入审计系统,以便事故发生时重放。

示例直接读取 PEM 私钥只是为了展示接口。不要把它照搬到公网服务。生产签名接口应该只接收规范化后的 Manifest 和资产引用,拒绝调用方自由指定证书、时间戳地址或任意外部 URL,并限制单次文件大小和媒体解析资源,避免解析炸弹与服务端请求伪造。

6. 验证结果不能只判断"有没有错误"

SDK 返回的是 Manifest Store 和 validation status。不同版本、不同资产会出现不同状态码,应用层不应把所有非空状态列表都当成同一种失败。更安全的方法是保留原始验证报告,再根据业务策略归类。以下代码展示一个与具体 UI 解耦的最小策略层:

python 复制代码
# provenance_policy.py
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Any, Iterable


class Verdict(str, Enum):
    NO_CREDENTIAL = "no_credential"
    INVALID = "invalid"
    VALID_UNKNOWN_SIGNER = "valid_unknown_signer"
    TRUSTED_PROVENANCE = "trusted_provenance"


@dataclass(frozen=True)
class Decision:
    verdict: Verdict
    reasons: tuple[str, ...]
    may_auto_publish: bool


FATAL_PREFIXES = (
    "claimSignature.",
    "assertion.dataHash.mismatch",
    "assertion.hashedURI.mismatch",
    "hardBindings.",
)


def status_codes(items: Iterable[dict[str, Any]]) -> tuple[str, ...]:
    return tuple(
        str(item.get("code", "unknown"))
        for item in items
        if not item.get("success", False)
    )


def classify(report: dict[str, Any]) -> Decision:
    manifests = report.get("manifests") or {}
    active = report.get("active_manifest")
    if not manifests or not active:
        return Decision(
            Verdict.NO_CREDENTIAL,
            ("资产没有可读取的 Content Credential",),
            False,
        )

    codes = status_codes(report.get("validation_status") or ())
    fatal = tuple(
        code for code in codes
        if any(code.startswith(prefix) for prefix in FATAL_PREFIXES)
    )
    if fatal:
        return Decision(Verdict.INVALID, fatal, False)

    # 字段名由接入层根据实际 SDK 报告规范化,不能只看签名成功。
    trusted = bool(report.get("policy", {}).get("trusted_signer"))
    if not trusted:
        return Decision(
            Verdict.VALID_UNKNOWN_SIGNER,
            codes or ("签名有效,但签发者不在业务信任列表",),
            False,
        )

    return Decision(
        Verdict.TRUSTED_PROVENANCE,
        codes or ("来源链和内容绑定通过当前策略验证",),
        True,
    )


def _self_check() -> None:
    assert classify({}).verdict is Verdict.NO_CREDENTIAL
    broken = {
        "manifests": {"m1": {}},
        "active_manifest": "m1",
        "validation_status": [
            {"code": "claimSignature.mismatch", "success": False}
        ],
    }
    assert classify(broken).verdict is Verdict.INVALID


if __name__ == "__main__":
    _self_check()

这里故意把 SDK 报告规范化和业务策略分开。SDK 负责按照规范验证结构、摘要、签名、时间戳和证书;策略层负责决定哪些签发者可用于自动发布、哪些只能展示、哪些必须阻断。实际接入时,应根据所用 SDK 版本的真实状态码建立显式映射,未知错误默认降级为人工复核,而不是用字符串中是否包含 valid 这种脆弱判断。

may_auto_publish=True 的含义也只能是"满足当前发布来源策略",不能显示成"内容真实"。如果业务还要求商品图不得夸大、新闻图片不得移花接木,就必须运行相应的事实核查、素材授权、人工复核和内容安全流程。来源验证只完成其中一项控制。

7. 篡改实验应该怎么做

没有负向实验的签名功能不能算完成。最小测试矩阵至少包含五类样本。

第一类是正常签发文件。验证器应找到 active manifest,资产绑定、Assertions 哈希和签名均通过;如果使用测试证书,结果应表现为签名有效但来源未知,而不是受信。第二类是签发后改动像素并以原文件结构保存,验证应报告 hard binding 或相关摘要不匹配。第三类是直接修改 Manifest 中的动作文本,Claim 签名或 assertion hash 应失败。

第四类是完整删除 Manifest。系统应报告无凭证,而不是篡改。因为很多合法旧内容从未使用 C2PA,缺失只代表没有这类来源证据。第五类是用另一张测试证书签发。签名本身可以有效,但业务信任列表不包含该证书时,不能自动通过。再增加证书过期、撤销、时间戳不可用、外部 Manifest 超时和 ingredient 链断裂等样本,就能覆盖生产环境主要分支。

测试时不要只截图 UI。保存原始文件、验证器版本、信任列表版本、完整 JSON 报告和最终策略判定。C2PA 2.4 中的 crJSON 是供配置评估、互操作测试和验证报告使用的派生视图,本身不是可独立验证的原始凭证。归档 crJSON 有助于审计,但不能用它替换原资产、Manifest Store 和签名材料。

8. 时间戳、撤销与证书轮换

证书有有效期,私钥也可能泄露。如果只判断"现在证书是否过期",大量历史内容会在证书轮换后突然失去可验证性。RFC 3161 可信时间戳可以证明签名在某个时间已经存在,并帮助验证器结合当时的证书状态判断。C2PA 2.x 还规定了相关时间戳和撤销处理流程。

生产签名服务应配置受认可的 TSA,并监控时间戳请求失败率。是否允许"签名成功但时间戳失败"的结果进入发布链,要由风险等级决定。内部草稿可以标记后重试,面向公众的高价值媒体通常应阻断。不要在 TSA 故障时悄悄写入本机时间,它既不能提供独立证明,也可能因时钟漂移造成错误历史。

证书轮换要保留三个维度:新任务从切换时刻开始使用新证书,旧资产继续保留原签名,验证端同时加载必要的当前和历史信任材料。发现私钥疑似泄露时,应立即停止签名、吊销或采取证书策略规定的处置、定位受影响时间窗,并依据可信时间戳区分泄露前后的签名。直接重新签署所有旧资产会抹平原始时间线,不应作为默认补救。

9. 编辑、截图和平台转码后的凭证会怎样

数字媒体在传播中经常被压缩和截图。硬绑定对字节或规范定义的媒体结构敏感,所以未经 C2PA 感知的编辑往往使原凭证失效或丢失。正确的编辑器不是"保留旧签名并假装没变化",而是验证输入,把输入作为 ingredient,在输出上生成新的 Manifest,记录自己的动作并签名。这样历史链继续存在,当前输出也有独立绑定。

截图更复杂。截图产生的是新资产,不能继承被截图内容的原签名。截图工具可以创建自己的凭证,声明屏幕采集动作,并把可获得的来源作为 ingredient 或引用,但消费者仍要看清"谁截取了什么"。如果平台只保留截图而丢失原文件,原凭证不能凭空证明截图没有经过其他处理。

软绑定可以在嵌入数据被清除后帮助找回 Manifest,例如基于指纹查询近似资产,或者通过不可见水印定位仓库记录。但软绑定可能出现碰撞、误匹配或水印受损,找回的 Manifest 还必须继续经过绑定和签名验证。产品不能因为"搜到了相似图"就展示完整绿色可信标识。

10. 用户界面必须忠实表达不确定性

C2PA 不只是后端加密功能。用户最终如何理解验证结果,取决于界面。首页可以展示简洁状态,但要允许用户展开查看签发者、签发时间、创建与编辑动作、输入材料以及验证问题。状态文案应描述证据,不应替用户下事实结论。

例如,"来源信息已验证:由某发布工具签署,文件自签署后未检测到受保护内容变化"比"这是真图"准确;"凭证无法验证:内容绑定不匹配"比"这是 AI 假图"准确;"未找到内容凭证"比"来源不可信"准确。用户还应知道签名者是软件产品、设备还是组织身份。C2PA 的基础信任模型聚焦签署 Claim 的机器身份,若要表达个人或组织身份,需要结合 CAWG 等相应规范与业务校验,不能从软件证书名称自行推断作者实名。

在批量审核后台,原始状态码、证书路径和 ingredient 关系可以提供给专业人员;在公众界面,应该用分层披露减少认知负担。无论哪种界面,都不要隐藏失败原因,也不要把"未知"涂成成功。错误的绿色徽章会比没有徽章更危险,因为它制造了错误确定性。

11. 生产架构中的安全边界

一套可上线的系统通常拆成签发服务、验证服务、策略服务、凭证仓库和审计存储。签发服务接触私钥,网络权限最小;验证服务处理不可信媒体,应放在隔离进程或沙箱,限制文件大小、解码时长、内存和外部请求;策略服务维护信任列表、允许的签发者与业务规则;审计存储保存不可变事件,但应避免无期限保存用户原始隐私媒体。

外部 Manifest 获取必须防止 SSRF。验证器不能盲目访问资产内声明的任意内网地址,应限制协议、目标域、重定向次数、响应大小和超时,并通过专用出口代理访问。离线或高安全环境可以关闭远程获取,将外部凭证先进入受控仓库。解析图片、视频和压缩容器时还要防止畸形文件与资源耗尽,SDK 最新并不等于外围媒体库没有漏洞。

信任列表是安全配置,不是普通业务数据。更新前要验证签名或发布渠道,记录版本和生效时间,支持快速回滚。业务自定义信任锚要区分环境,测试根证书绝不能混入生产。策略变化后,不要静默改写历史判定;应该保留"当时按哪个列表得出什么结果",同时允许用新策略重新评估。

隐私同样重要。来源历史可能暴露设备、地点、人员或内部工作流。发布者应遵循最小披露原则,只写实现用途所需的 Assertions;消费者不应因为技术上能够读取就无限收集。需要删除敏感声明时,应使用规范允许的编辑和新 Manifest 表达,而不是二进制修改旧 Claim 后继续沿用旧签名。

12. 与事实核查和 AI 检测怎样协作

一个稳妥的内容可信系统应把多类证据并列,而不是互相覆盖。C2PA 回答来源链和完整性;反向图片搜索寻找更早版本与传播上下文;新闻事实核查验证时间、地点、人物和事件;取证工具分析剪辑痕迹;AI 检测器提供有限概率信号;人工审核处理冲突和高风险判断。

假设某张图片具有完整、受信的 C2PA 链,签发者确实是知名机构,文件也没有被篡改,但标题把旧照片描述成今天的事件。C2PA 会正确验证"该机构曾签署这张旧照片",却无法证明当前标题为真。反过来,一位目击者用不支持 C2PA 的老设备拍下真实事件,文件没有凭证,也不能因此判假。这两个例子说明:来源证据提高可问责性,却不会消除认识世界所需的调查。

策略引擎应允许这些信号保持独立。不要用一个总分把所有问题混成 87 分可信,因为使用者看不出分数来自受信签名、事实核查还是模型判断。更好的输出是结构化证据面板:来源链状态、内容绑定状态、签发者信任状态、事实核查状态、检测器结果及其版本。做自动化决策时,再按具体场景设定明确门槛。

13. 如何验收一条跨工具来源链

单文件签名成功只是起点,真正的互操作验收要让资产经过至少两个独立工具。可以准备一张测试原图,由采集端生成第一份 Manifest;再让编辑端导入它、执行一次可见裁切并输出第二份凭证;最后由另一套验证器读取结果。验收人员应确认第二份凭证把第一份资产列为 ingredient,当前资产绑定通过,两个阶段的动作没有被合并成一段无法区分的文字,并且每个签名分别连接到预期的信任配置。

随后做断链实验。删除前序原图但保留最终文件,验证器仍应能够检查最终文件自己的绑定和签名,同时明确指出 ingredient 能否解析,而不是把整条链简单判成一个模糊错误。再把原图替换为同名文件,ingredient 摘要必须不匹配。若系统只凭文件名或 URL 认定前序材料一致,就说明来源关系没有建立在密码学绑定上。

还要测试跨版本读取。新版本生成器不得写入规范中已经弃用的结构,但验证器为了兼容历史资产,可能仍需读取旧结构。接入层应该记录生成器、验证器和规范声明版本;遇到未知 assertion 时保留原始数据并给出受控提示,不能因为一个非关键扩展无法展示就忽略已经发现的签名错误,也不能因为核心签名通过就假定所有扩展语义可信。

验收报告应把"规范验证"和"产品策略"分成两栏。前一栏列出 Manifest、Assertions、内容绑定、签名、时间戳和证书路径的结果;后一栏列出签发者是否在企业允许列表、动作是否符合发布政策、是否需要事实核查。这样即使策略以后变化,也能基于原始验证证据重新判断,而不是重新猜测当时为什么显示绿色标识。

对于将同一素材分发到多个渠道的团队,还应分别保存各渠道下载样本,因为不同转码链可能产生完全不同的凭证保留结果,不能用源站验证成功代替终端验收。

14. 上线前检查清单

上线前可以沿着生产链逐项检查:生成和编辑工具是否准确记录动作与 ingredient;签名私钥是否位于 KMS 或 HSM;证书是否符合 C2PA 要求并连接到目标信任体系;是否配置可信时间戳;签后是否立即验证;平台转码是否保留凭证或生成新的来源链;验证器是否区分无凭证、无效、未知签发者和受信来源;外部 Manifest 获取是否有网络隔离;原始报告和策略版本是否可审计;界面是否避免使用"真图""假图"这类越界结论。

还要做真实传播链测试。把签发样本依次上传到实际 CDN、内容管理系统、移动端和社交平台,再下载验证。实验室里通过不代表经过缩略图、格式转换和元数据清理后仍可用。发现某个环节剥离凭证时,应决定由该环节生成新 Manifest、保留原文件下载入口,还是部署软绑定恢复,而不是在前端假装凭证还在。

最后建立运行指标:签发成功率、签后验证失败率、时间戳失败率、凭证保留率、未知签发者比例、外部 Manifest 获取时延、策略阻断量和人工复核结果。指标异常时要能追溯到 SDK 版本、证书、信任列表和分发节点。只有可监控、可重放、可解释,内容凭证才从展示功能变成可靠基础设施。

15. 总结

C2PA 的核心不是给 AI 图片贴标签,而是建立一条由结构化声明、内容绑定、数字签名、证书信任和时间戳组成的可验证来源链。Content Credential 记录内容从哪里来、经历了哪些被声明的动作;验证器检查这些声明是否仍与资产匹配、签名是否有效、签发者是否属于当前信任体系;业务系统再结合场景决定展示、阻断或人工复核。

它真正解决的是"谁对什么声明负责,以及声明和资产有没有被改",不是"画面讲述的事情一定真实"。把这条边界写进架构、代码、测试、策略和 UI,才能避免用密码学制造新的误导。对事实真伪的判断仍需要上下文、交叉证据和人工调查。验证来源链不等于判断内容真假,但没有可靠来源链,很多事实核查会失去关键起点。

参考资料

  1. C2PA Content Credentials Technical Specification 2.4
  2. C2PA 2.4 Security Considerations
  3. C2PA and Content Credentials Explainer 2.4
  4. C2PA Guiding Principles
  5. C2PA Conformance Program and Trust Lists
  6. CAI Open Source SDK Documentation
  7. CAI SDK: Signing with Local Credentials
  8. Official c2pa-python Repository
相关推荐
zhousenshan41 分钟前
AgentScope 和 Spring AI Alibaba的区别
人工智能
奈落2441 分钟前
【RAG 深度修炼】专栏 · 第 9 期(收官):安全专题与生产 Checklist 总集——从能跑的 Demo 到睡得着觉的生产系统
大数据·网络·人工智能·安全·ai编程
超大青花鱼42 分钟前
Ubuntu20.04安装CUDA11.8教程
人工智能·深度学习·计算机视觉
golang学习记1 小时前
Jetbrains 正式官宣:全新AI IDE正式发布!
ide·人工智能·intellij-idea·air
HZjiangzi1 小时前
深耕精密驱动二十六载,捷昌驱动助力机器人产业升级发展
人工智能·算法
鬓戈1 小时前
Claude Code frontend-design 插件调研 与 Vue 旧系统风格一致性方案
前端·vue.js·人工智能
BSD_CGQ1 小时前
柔性触觉到工业称重:FSR 高精度工程化适配路径解析
大数据·人工智能·压力传感器·柔性薄膜压力传感器
一隅论数智1 小时前
给AI找对“富矿“:本体协同的六大应用模式与落地战法
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
HZjiangzi1 小时前
从传统精密制造到机器人赛道:捷昌驱动旋转模组技术突围之路
人工智能