跳到正文
DEV Community· Check AI Free·· 3 小時前AI 評分55

截圖後哪些資料仍會保留?EXIF、XMP 與 C2PA 的小型可重複測試

What survives a screenshot? A small, reproducible test of EXIF, XMP and C2PA metadata

AI 導讀

Check AI Free 作者以 8 張照片測試 JPEG 匯出與瀏覽器截圖,兩種處理均未保留原有相機資料、拍攝時間、編輯軟件及 XMP 容器。JPEG 匯出時沒有傳入中繼資料;樣本不含 AI 生成圖,亦未測試社交平台或驗證 C2PA 簽章,因此結果只適用於這組本機設定,並非 AI 分類準確度。

正文

Disclosure: I work on Check AI Free, a free image checker. The tools and the MCP server mentioned below are ours. The test numbers come from our published research page, and I have tried to keep the limits as visible as the findings.

A common question when someone asks "is this image AI-generated?" is: doesn't the file just say so? Sometimes it does. Generators, cameras and editors write metadata, and a growing number write signed C2PA manifests ("Content Credentials"). But the file you are looking at is rarely the file that was created. It has usually been exported, screenshotted, forwarded or re-encoded on the way to you.

This post covers three things:

  1. Where provenance metadata actually lives inside a JPEG or PNG.
  2. What a small, reproducible test found when files went through a metadata-free JPEG export and a browser screenshot.
  3. How to check a file yourself, in the browser, on the command line, or from an MCP-capable agent, and what none of these can tell you.

Where the metadata lives

An image file is a container plus compressed pixels. The metadata is in separate blocks next to the pixel data, not in the pixels. That one fact explains most of what follows.

EXIF is a small TIFF directory. In a JPEG it usually sits in an APP1 segment that starts with the bytes Exif. It can hold camera make and model, the software that last saved the file, orientation, exposure, DateTimeOriginal, and a nested GPS directory. PNG can carry the same TIFF block in an eXIf chunk, and WebP in an EXIF chunk.

XMP is an XML packet. In JPEG it is a second APP1 segment that starts with the Adobe XMP namespace. It can repeat EXIF-style fields, carry captions and credits, and also carry a Content Credentials description. An XMP packet is still just text, though. It is not a signed manifest.

IPTC is the caption and credit block used in news and stock workflows. In JPEG it is usually inside a Photoshop APP13 resource.

C2PA (Content Credentials) is the one with cryptography. A manifest contains a claim (CBOR, naming a claim generator), an assertion store (labeled records such as c2pa.actions and c2pa.ingredient), and a signature box holding a COSE signature and usually the signer's certificate. In a JPEG the manifest store is split across APP11 segments whose payload starts with JP. In a PNG it is a caBX chunk.

For AI provenance, the field to look for inside C2PA is the digital source type on an action. The IPTC value trainedAlgorithmicMedia (or a composite type that names it) means the manifest asserts that a trained generative model was used. That is a statement in the file, not a visual guess. It is also only as trustworthy as the signature, the certificate chain, and the binding between the manifest and these exact pixels.

Two common misreadings:

  • "Content Credentials present" ≠ "AI". Cameras and editors sign ordinary photos too. A camera manifest will normally say created with a non-generative source type.
  • "No manifest" ≠ "authentic". Many cameras never wrote one, many generators still don't, and many export paths drop APP11 and caBX.

Stable Diffusion and ComfyUI outputs are a separate case. They often store the prompt, parameters or a whole workflow JSON in PNG text chunks. That is unsigned text. A workflow is not a Content Credential, and a Content Credential is not a prompt.

The test: what survives an export and a screenshot?

Because all of this lives in containers, anything that writes a new file without copying those containers loses it. We wanted to measure that with a protocol someone else could repeat, rather than just assert it. The write-up is Image metadata survival, Edition 1.

Setup (protocol v1):

  • 8 photographic originals, all by one photographer (Thomas Bresson, CC BY 2.0, from Wikimedia Commons). That is a convenience sample: 0 generated images. The selection rule: the first ten results of a Commons search for filetype:bitmap Thomas Bresson, alphabetized, minus two originals over 10 MiB.
  • Each original was kept unchanged and SHA-256 hashed. The hashes are published, so you can re-download the same bytes and check them.
  • Transform A, JPEG export: apply EXIF orientation to pixels, convert to RGB, resize the longest side to at most 1536 px with LANCZOS, then save with Pillow 12.3.0 at quality 85 and subsampling 2, with no metadata passed to the encoder.
  • Transform B, browser screenshot: show the original in an 800 × 600 white viewport (contain sizing), then capture a PNG at device scale 1 with Chromium 152.0.7977.54.
  • That gives 24 files: 8 originals plus 16 transformed copies.
  • Field presence was read independently with Pillow (including the EXIF sub-IFD). Separately, the site's own header reader looked at the first 64 KiB of each file. There were 0 reference parse errors.

Results (retention counts only originals that had the field):

Field In originals Kept after JPEG export Kept after screenshot
Camera make 8/8 0/8 0/8
Camera model 8/8 0/8 0/8
Capture time 8/8 0/8 0/8
Editing software 8/8 0/8 0/8
XMP container 8/8 0/8 0/8
Generation text 0/8 not measured not measured

The site's header reader, measured on its own, found a source clue in 8/8 originals, 0/8 JPEG exports and 0/8 screenshots.

A repeat run in an isolated Python environment with the same pinned Pillow version reproduced all 24 field-presence and product-signal observations. All eight original hashes matched. One screenshot PNG came out with different bytes between runs, but its observed fields did not change. Browser captures are not guaranteed to be byte-identical.

How to read this, and how not to

This result is not surprising, and I'm not presenting it as if it were. The JPEG export deliberately passed no metadata, and a screenshot is a new picture of the screen. The value of the test is that the claim "a screenshot can look like the original and still have none of its source fields" now has a fixed protocol, published hashes and a repeat run behind it.

What it does not show:

  • No social platform was tested. There was no upload/download through Instagram, X, WhatsApp, Facebook or Discord. The numbers describe these exact local settings and nothing else.
  • No AI-generated images were in the batch. Generation-text retention, including A1111/ComfyUI parameters, was not measured.
  • C2PA signatures were not verified. The XMP row counts XMP containers. An XMP container is not necessarily a Content Credentials manifest.
  • Field retention is not detection accuracy. These are field-presence counts, not a measure of correct AI classification. No paid pixel-model checks were run on this batch.

Before generalizing, the next edition needs a broader, independently sourced batch that includes generated images. Earlier counts stay in the changelog.

How to check a file yourself

The order that works in practice: get the original if you can, read what the file says, keep the context, and only then look at a pixel-model score.

1. In the browser, no upload

  • The EXIF viewer reads EXIF, XMP and IPTC in JPEG (APP1/APP13), PNG (eXIf and text) and WebP locally. The file is not uploaded, which matters because GPS coordinates are visible to anyone holding the file.
  • The C2PA checker walks the APP11 / caBX manifest store and prints the claim generator, the signer name from the first certificate, the recorded actions, the ingredients, and any trainedAlgorithmicMedia assertion. It does not verify the signature, the certificate chain, or the pixel binding. For a validation you can rely on, use the issuer's own checker or the Content Authenticity Initiative's verify tool.

If you're about to share a photo, strip the metadata from a copy and keep the original. Read the fields before you strip them, because a missing manifest is hard to argue about later if you never recorded that it was there.

2. On the command line

If you want something scriptable, the usual open tools work fine:

# Dump every metadata group exiftool knows about
exiftool -a -G1 photo.jpg

# Print a C2PA manifest store, if one is present (CAI's c2patool)
c2patool photo.jpg

And a tiny Pillow check of the kind the test used for field presence:

from PIL import Image

img = Image.open("photo.jpg")
exif = img.getexif()
print("make/model:", exif.get(0x010F), exif.get(0x0110))
print("software:", exif.get(0x0131))
print("DateTimeOriginal:", exif.get_ifd(0x8769).get(0x9003))
print("XMP present:", "xmp" in img.info or b"http://ns.adobe.com/xap/1.0/" in open("photo.jpg", "rb").read(65536))

3. From an agent (MCP)

We also publish an MIT-licensed MCP server, hyai-dev/checkaifree-mcp (npm: checkaifree-mcp). It speaks stdio and needs no API key:

{
  "mcpServers": {
    "checkaifree": {
      "command": "npx",
      "args": ["-y", "checkaifree-mcp"]
    }
  }
}

It registers two tools: detect_ai_image (takes image_url, file_path or image_base64) and inspect_c2pa_provenance (takes image_url or file_path; the name is historical). Some things to know before you wire it into anything:

  • It uploads the image. Both tools send the selected bytes to the Check AI Free /api/v1/detect header endpoint. This is not a local-only checker, so don't feed it sensitive images.
  • It reads headers only. It doesn't call the pixel-model route, so a file with no metadata can come back inconclusive. It prints a model percentage only for a completed pixel-classifier score, which this adapter never produces.
  • It doesn't verify C2PA. A C2PA line means a source marker was or wasn't observed in the inspected bytes, not that a signature checked out.
  • Inputs are JPEG, PNG and WebP, up to 10 MiB.

The developers page documents the endpoint and the response fields (result.verdict, result.ai_probability, the signals array, classifier_available). On the header route, ai_probability comes from the metadata reader, not a model.

The limits, stated plainly

  • Metadata is editable. Camera make, model and capture time are stored text. They can be copied onto another file, left stale by an editor, or removed.
  • Missing fields prove nothing either way. Empty is a real result, and it's the usual result after a screenshot. It is a property of this copy, not of the scene.
  • A signed manifest is the strongest file-level signal, but only once it's validated. Reading a manifest isn't the same as validating it, and a manifest can be copied onto other bytes.
  • Stripping metadata doesn't change pixels. Removing a prompt or a manifest removes a claim. It does not make a synthetic image look like a photo to a pixel model or to a person.
  • Pixel detectors are a separate, probabilistic question. Keep their output next to the file evidence and the context. Don't let it replace them.

If you run your own version of the protocol, especially with real platform uploads or generated images, I'd like to compare notes. The methodology and hashes are on the research page, so you can start from the same files.

來源:DEV Community · dev.to