Photo Forensics — 照片元数据取证
工具型 skill:用户给一张图片(本地路径或 URL),输出一份完整的 EXIF 报告,包含:
1. EXIF 全字段读取与解读
2. 字段含义科普(让普通用户看懂)
3. 隐私泄露风险评估
4. AI 合成线索扫描 + 概率区间(非确定性断言)
何时触发
满足以下任一条件即调用本 skill:
- 用户提供图片路径/URL,问「这图的 EXIF / 元数据」
- 用户问「这张照片会泄露隐私吗 / 能定位我吗」
- 用户问「这张图是不是 AI 生成的 / 是不是实拍」
- 用户问「照片的拍摄参数 / 相机型号 / 拍摄时间」
不触发:用户没有给具体图片路径,只是泛泛问 EXIF 概念;或要求生成一个 EXIF 小程序项目(那是 exif-tool skill 的职责)。
执行流程
收到图片路径后,按以下步骤执行:
步骤 1:探测读取工具(三档回退)
优先级:exiftool CLI → python exifread/Pillow → node piexifjs。
直接调用本 skill 自带的 Python 主脚本(已实现三档回退逻辑):
python "<skill_dir>/scripts/read_exif.py" "<image_path_or_url>"
- Windows 环境用
py或python均可 - 脚本会自动探测可用工具并输出统一 JSON
- 若 Python 不可用,回退到 Node 脚本:
node "<skill_dir>/scripts/read_exif.js" "<image_path>" - 若 URL 远程图,脚本会先下载到临时文件再解析
输出 JSON 结构:
{
"source": "exiftool|python|node",
"file": { "size_bytes": 1234567, "format": "JPEG" },
"fields": { "Make": "Apple", "Model": "iPhone 15 Pro", ... },
"gps": { "latitude": 39.9042, "longitude": 116.4074, "altitude": 43.2 } | null,
"warnings": ["missing EXIF segment", ...]
}
步骤 2:按字段词典解读
读取下文「EXIF 字段词典」,对 fields 中的关键字段逐条给出人类可读解释。仅解读实际存在的字段,缺失字段不提。
步骤 3:隐私风险评分
按「隐私风险规则」对 GPS/设备/时间/软件四类字段评分,给出 low/medium/high 等级与具体泄露点。
步骤 4:AI 合成线索扫描
按「AI 合成线索清单」逐条检查,命中则记录线索与权重,最后合成概率区间(低/中/高)。
步骤 5:输出报告
严格按「报告模板」输出,不要自创格式。报告末尾必须附上步骤 6 提示(如果隐私风险为 low 且用户没主动要求清除则可省略)。
步骤 6:(可选)清除隐私 EXIF
> 必须严格遵守:清除是破坏性操作,先给报告再询问用户是否要清除,确认后再执行。绝不静默修改用户文件。绝不覆盖原图,始终输出新文件。
清除调用:
python "<skill_dir>/scripts/strip_exif.py" "<input_path>" [-o output_path]
# 或 Node 回退
node "<skill_dir>/scripts/strip_exif.js" "<input_path>" [-o output_path]
输出 JSON:{ "source": "exiftool|pillow|binary|piexifjs", "input": "...", "output": "...", "removed_bytes": 1234 }
清除后必须再跑一次步骤 1(read_exif),确认清除结果并展示给用户。
清除策略三档回退:
1. exiftool -all= — 最彻底,删除所有元数据(EXIF+XMP+ICC+IPTC+GPS)
2. Pillow save(exif=b'') — 重新编码清空 EXIF,可能轻微质量损失
3. 自实现 JPEG 二进制段裁剪 — 0 画质损失(跳过 APP1~APP15/COM 段,保留 SOI/APP0/DQT/SOF/DHT/SOS/EOI)
非 JPEG 格式(PNG/WebP/HEIC):二进制裁剪方案不适用,exiftool 或 Pillow 方案可能仍有效。如果三档都失败,报告中说明原因并给手动清除建议。
EXIF 字段词典
解读时从下表取含义,保持口语化:
| 字段 | 含义 | 隐私相关 |
|---|---|---|
| Make | 相机厂商(Apple/Samsung/Canon) | 中 |
| Model | 相机/手机型号(iPhone 15 Pro) | 中 |
| LensModel / LensSpecification | 镜头型号与规格 | 低 |
| Software | 处理软件(Photoshop、Lightroom、Midjourney) | AI 关键 |
| DateTime / DateTimeOriginal / DateTimeDigitized | 修改/拍摄/数字化时间 | 低 |
| ExposureTime | 快门速度(1/200 秒) | 无 |
| FNumber | 光圈(f/1.8) | 无 |
| ISO | 感光度 | 无 |
| FocalLength / FocalLengthIn35mmFormat | 焦距 / 等效 35mm 焦距 | 无 |
| ExposureMode / MeteringMode / WhiteBalance | 曝光/测光/白平衡模式 | 无 |
| Flash | 闪光灯状态 | 无 |
| GPSLatitude/Longitude/Altitude | 经纬度海拔 | 高 |
| ImageWidth / ImageHeight | 像素尺寸 | 无 |
| BitsPerSample | 位深度 | 无 |
| Orientation | 方向标记 | 无 |
| Artist / Copyright | 作者/版权 | 中 |
| MakeNote | 厂商私有扩展(含大量子字段) | 中 |
| C2PA / ContentCredentials | 内容来源认证(部分 AI 工具嵌入) | AI 关键 |
| XMP、ICC_PROFILE | XMP 元数据 / 色彩配置 | 低 |
隐私风险规则
按字段命中情况分级:
- 高风险:存在 GPS 坐标(可定位到街道级地址)
- 中风险:存在 Make+Model(关联设备身份)或 Artist/Copyright
- 低风险:存在 DateTimeOriginal(精确到秒的时间)或 Software(系统版本可被利用)
输出:level(low/medium/high)+ risks 数组(每条含 field/description/severity)+ 建议动作(清除 EXIF、剥离 GPS 等)。
AI 合成线索清单
逐条检查,每条命中累计权重,最终按权重总和映射概率区间:
| 线索 ID | 检查项 | 权重 | 说明 |
|---|---|---|---|
| A1 | Software 含 AI 工具名 | 5 | 关键词:Midjourney、DALL-E、Stable Diffusion、Adobe Firefly、GPT-4o、Leonardo、Runway、Sora、Nano Banana、Imagen |
| A2 | Software 含 AI 后处理但无相机字段 | 3 | 如 Photoshop 但无 Make/Model/LensModel |
| A3 | 存在 C2PA / ContentCredentials 声明 | 4 | AI 工具主动嵌入来源链,但正规相机水印工具也可能用 |
| A4 | 完全无 EXIF 但格式为 JPEG 且声称实拍 | 2 | 多数 AI 生成图无 EXIF,但压缩传输也会丢失 |
| A5 | DateTimeDigitized 与 DateTimeOriginal 错位超 1 年 | 1 | 可能是后期合成或重编码 |
| A6 | MakeNote 缺失但 Software 存在 | 2 | 实拍通常有厂商扩展段 |
| A7 | ImageWidth/Height 为常见 AI 输出尺寸(1024/2048/1344×768 等) | 1 | 弱线索,仅供参考 |
| A8 | JPEG 量化表指纹异常 | 3 | 需 exiftool 高级输出,无工具时跳过 |
概率区间映射(权重总和 W):
- W = 0:无显著线索,疑似实拍(概率 < 10% AI)
- W 1–3:低概率 AI(约 10–30%)
- W 4–6:中概率 AI(约 30–60%)
- W ≥ 7:高概率 AI(> 60%)
重要:输出必须强调「这是基于元数据线索的启发式判断,非确定性证据。AI 生成图可能被人为剥离元数据,实拍图也可能因压缩丢失 EXIF。最终判定需结合视觉分析或专业鉴伪工具。」
报告模板
输出严格遵循以下结构(Markdown):
# 照片取证报告
**文件**:<文件名> · <大小> · <格式>
**读取方式**:<exiftool/python/node>
## 一、拍摄设备
- **厂商**:<Make 或「未记录」>
- **型号**:<Model 或「未记录」>
- **镜头**:<LensModel 或「未记录」>
> 解读:<用一句话解释这些字段告诉了我们什么>
## 二、拍摄参数
- **快门**:<ExposureTime,如 1/200 秒>
- **光圈**:<FNumber,如 f/1.8>
- **ISO**:<ISO>
- **焦距**:<FocalLength>(等效 <FocalLengthIn35mmFormat>)
- **闪光灯**:<Flash 状态>
- **曝光模式**:<ExposureMode>
> 解读:<一句话>
## 三、时间信息
- **拍摄时间**:<DateTimeOriginal>
- **数字化时间**:<DateTimeDigitized>
- **修改时间**:<DateTime>
> 解读:<是否一致 / 异常>
## 四、位置信息
<若 GPS 存在:>
- **经纬度**:<lat>, <lng>
- **海拔**:<altitude>
- **大致区域**:<省份/城市,若可推断>
> ⚠️ 高风险:此坐标可定位到街道级地址
<若无 GPS:> 未记录 GPS 信息
## 五、隐私风险
**等级**:<low/medium/high>
<逐条列出 risks,每条:字段 / 描述 / 严重程度>
**建议**:<清除 EXIF / 剥离 GPS / 不分享原图等>
## 六、AI 合成线索分析
**概率区间**:<低/中/高>(权重 W = <数值>)
<逐条列出命中的线索,每条:线索 ID + 描述 + 权重>
<若无命中:> 未发现显著 AI 生成线索
> ⚠️ 注意:此判断基于元数据启发式分析,非确定性证据。AI 生成图可能被人为剥离元数据,实拍图也可能因压缩丢失 EXIF。最终判定需结合视觉分析与专业鉴伪工具。
## 七、完整 EXIF 字段
<以表格或列表展示所有原始字段,方便高级用户查阅>
| 字段 | 值 |
|---|---|
| ... | ... |
## 八、清除结果(仅当用户确认执行后展示)
**清除方式**:<exiftool/pillow/binary/piexifjs>
**清除前大小**:<原始字节数>
**清除后大小**:<清除后字节数>
**剥离字节**:<removed_bytes>
**输出文件**:`<input>_cleaned.jpg`
**清除后复查**:
<再跑一次步骤 1 的 EXIF 读取结果。若已无 EXIF 字段,显示「✅ 元数据已完全清除」;若仍有残留,列出残留字段并可能需要换更高档清除方案。>
实现脚本
本 skill 自带四个脚本,agent 按运行时环境调用:
读取 EXIF:
- scripts/read_exif.py — Python 主实现,三档回退(exiftool → exifread/Pillow → 自实现 TIFF 解析)
- scripts/read_exif.js — Node 回退实现(piexifjs + 自实现 TIFF 解析)
清除 EXIF:
- scripts/strip_exif.py — Python 清除器,三档回退(exiftool -all= → Pillow save → JPEG 二进制段裁剪)
- scripts/strip_exif.js — Node 清除器,两档回退(piexifjs remove → JPEG 二进制段裁剪)
调用约定:读取优先 Python,失败回退 Node。清除同理。所有输入输出格式一致(JSON)。清除始终输出新文件,不覆盖原图。
使用示例
用户:「帮我看看 D:/photo.jpg 的 EXIF,是不是 AI 生成的」
执行:
1. python "<skill_dir>/scripts/read_exif.py" "D:/photo.jpg"
2. 解析 JSON
3. 按报告模板输出
用户:「https://example.com/cat.jpg 这图有隐私泄露吗」
执行:
1. python "<skill_dir>/scripts/read_exif.py" "https://example.com/cat.jpg"(脚本自动下载)
2. 按报告模板输出,重点呈现「四、位置信息」与「五、隐私风险」
用户:「帮我清掉 D:/photo.jpg 的 EXIF」
执行:
1. 先跑 read_exif 出报告(步骤 1-5),展示当前隐私风险
2. 询问用户确认:「是否确认清除?将生成新文件 D:/photo_cleaned.jpg,原图不修改」
3. 用户确认后:node "<skill_dir>/scripts/strip_exif.js" "D:/photo.jpg"
4. 再跑一次 read_exif 验证清除结果
5. 按报告模板第八章展示清除结果
注意事项
- 远程图必须先下载,注意 URL 可达性与大小限制
- exiftool 若未安装,脚本自动回退,无需提示用户安装
- AI 判定永远不能 100%,必须保留「非确定性」声明
- 隐私建议要具体可执行(如「用 exiftool -all= 清除」或「微信发送时勾选原图关闭」)
- 不上报用户图片内容,仅分析元数据
- 清除前必须询问用户确认,绝不静默执行破坏性操作
- 清除绝不覆盖原图,始终输出
_cleaned后缀新文件 - 清除后必须再读一次验证,展示清除效果给用户