PDF 元数据与隐藏数据
最后更新:
文档元数据是文件在页面内容之外携带的关于自身的信息:字段声称的作者是谁、哪个应用生成了它、它声称自己何时创建与修改,以及哪些早期材料仍留在里面。一个 PDF 可以同时装有两套重叠的元数据系统、一条早期修订链、完整的嵌入文件和以不可见方式绘制的文本;一张 JPEG 照片装着相机的日记;甚至一张普通的 PNG 截图也会说出制作它的软件。本文逐一介绍这些藏身之处在哪里、为什么普通编辑抹不掉这些数据,以及真实泄漏是如何发生的。想边读边检查自己的文件,就运行文档取证工具——它在你的浏览器中本地工作;同一批文件的阅读结构方面,见你的 PDF 是网站无障碍的一部分。
每个 PDF 里都装着两套元数据系统
较老的一套是信息字典:存放在文件预告片中的一小组键值对——Title、Author、Creator(制作原始文档的应用)、Producer(制作 PDF 的应用)、CreationDate 与 ModDate。这些字段在导出时自动写入,而大多数文档作者从未打开过能看到它们的属性对话框——这正是它们泄漏的原因。从个人笔记本导出的报告,把笔记本账户名写成了作者;从去年模板开始的文档,仍写着去年的作者。它们在打印出的页面上什么都看不见,却对每个文件接收者只隔着一层属性面板。
较新的一套是 XMP:嵌入文档的 XML 包,建立在 Adobe 的可扩展元数据平台之上,与 PDF 规范本身 ISO 32000-2 一同被描述。XMP 用命名空间重复了许多同样的内容:dc:title 表示标题、pdf:Producer、xmp:CreateDate 与 xmp:ModifyDate,外加旧字典没有的字段,如创建工具。两套系统若保持同步本无害处,但它们并不:一个工具更新信息字典而忽略 XMP,另一个重写 XMP 而让字典过期,第三个剥掉一套留下另一套。于是单个文件常常对自己讲出两个略有不同的故事——而这种不一致本身就是信息,因为它记录了不止一个工具、在不止一个时刻经手过这份文档。
增量更新:删掉了却从未离开的段落
PDF 是为只追加编辑而设计的。编辑器增量保存修改时并不重写文件:它把改动的对象、一个新的交叉引用段和一个新的预告片追加到末尾,并通过 /Prev 指针链回上一个预告片。每次修订都以自己的 %%EOF 标记结尾,所以数一数这些标记,就能得到该文件以此方式保存次数的下限——而每次早期修订都物理地保留在字节中且可读。在普通阅读器中打开这样的文件,你只看到最新修订;去读字节,或沿链回滚,文档的早期状态还在那里:表格里的旧数字、条款的上一版措辞、发布前被删掉的段落。
这正是屡屡见诸新闻的涂黑失败背后的机制。团队需要发布一份隐去姓名或数字的报告,于是有人在敏感段落上画黑色矩形——一种只改变页面外观、完全不触碰下层内容的操作——或在随后增量保存的编辑器里删除文字,让被删对象留在早期修订中。发布的文件看起来干净。几天后,有读者选中“涂黑”区域、复制、把原文粘进文本编辑器;或有研究者解析修订链,恢复了被移除的内容。真正的涂黑与遮盖是不同的操作:底层内容必须从内容流中移除,并把文件完整重写,让字节本身不再包含页面上不再显示的东西。若你即将发布的文件带有多个 %%EOF 标记,它的历史还装在里面——请假设有动机的读者会去读那段历史。
嵌入文件与隐藏文本:无人列出的货物
PDF 可以把完整的文件装在自己内部。该格式定义了嵌入文件名树,通过 /EmbeddedFiles 与 /Filespec 对象到达,创作工具用它存放源电子表格、数据附录和作品集文档。能揭示它们的附件面板是大多数读者从不打开的,所以嵌入的货物悄无声息地旅行:发布那份精美的 PDF 摘要,可能同时发布了它背后的工作电子表格,连同它自己的元数据、草稿表和批注。反过来对接收者同样成立——收到的 PDF 里的附件会继承周围文档的信任,这就是为什么嵌入文件值得在任何检查清单中占一行,而不是靠偶然发现。
文本不需要成为单独的文件也能隐藏。PDF 以渲染模式绘制文本,而模式 3——用 3 Tr 操作符设置——既不填充也不描边:字符在内容流中,可以被选择、搜索和提取,却在页面上完全不可见。有些不可见文本合法且有价值:扫描页面背后的 OCR 层正是如此,它是让图像可搜索的隐藏文本。问题在于其他落入模式 3 或其近亲的东西——白底白字、被裁到可见页面之外的文本、为搜索引擎添加的关键词层——因为提取文本层的读者会收到页面从未显示过的文字。当隐藏层与可见层不一致时,文件同时在说两件事,而其中只有一件经过校对。
图片也记日记:PNG 数据块与 JPEG Exif
PNG 文件是一串命名的数据块,其中几种块类型纯粹为携带文本而存在:tEXt、它的压缩兄弟 zTXt 和国际化的 iTXt。导出工具习惯性地写入它们——一个名为 Software 的关键字写着编辑器、一个创建工具字符串,有时是一整个嵌入的 XML 配置文件——而它们在每一次保留块清单的裁剪和缩放中存活。tIME 块添加最后修改时间戳,eXIf 块可以装下完整的相机元数据块,而追加在结束块 IEND 之后的字节被每个查看器忽略,却留在文件中。于是一张看起来只是窗口中性图片的截图,可能说出这台机器的软件、给自己标上日期,并携带作者从未见过的数据。
JPEG 照片走得更远。文件是一串标记段:APP0 装 JFIF 头,APP1 装 Exif 块或 XMP 包——有时两者都有。Exif 的 IFD0 字段读起来像相机的日记:设备的 Make 与 Model、照片拍摄或最后修改的 DateTime、处理它的 Software,在扩展目录中还有 GPS 坐标。直接从相机胶卷发布的手机照片曾让组织和个人出丑——把他们精确地定位在照片本身从未显示的时间和地点。注释(COM)段添加最后保存文件的工具留下的自由文本,尾随字节可以位于图像结束标记之后,而读取这一切的工具——Phil Harvey 的 ExifTool,列在来源中——是免费下载的,也就是说你的接收者也能读它们。
交叉核对时间戳,然后在发布前清理
单个时间戳是一项主张;多个时间戳是一个可以自我核对的故事。创建日期落在修改日期之后,不可能描述由一个工具一次性生成的文档。信息字典日期与 XMP 日期对同一事件记录不一致,记录了两个工具用两只时钟。落在未来的日期,记录了设错的时钟、被手工编辑的字段,或由碎片拼凑的文件。连 PDF 日期格式本身也招致歧义——D: 字符串可以省略时区,让读者猜测这些数字来自哪只时钟。像审计师对待账目差异那样对待每处不一致:它通常有无辜的解释——模板复用、笔记本时钟、在上传时重写文件的优化器——它是需要权衡的证据,本身永远不是判定。元数据字段任何人用免费工具都能编辑,所以它们只能佐证或反驳一个故事,不能证明它。
因此清理是一套流程,不是一个按钮。第一,检查:把文件放过文档取证工具,记下它会告诉陌生人什么——作者与工具名、修订数、嵌入文件、隐藏文本、相互争吵的时间戳。第二,尽量在源头修复:在创作应用中改正文档属性,而不是给它的输出打补丁。第三,导出一份全新的完整重写——打印为 PDF、专门的清理工具,或对图片用 ExifTool——因为完整重写会丢弃早期修订、孤儿对象和尾随数据,这是增量保存永远做不到的。最后,复查你真正发出的那份副本,而不是你打算发的那份。并记住全篇所依据的区分:内部原件应保留其元数据,因为溯源、归档价值、乃至无障碍——文档标题也是元数据——都依赖它。清理发布的副本,在家里把记录完整保留。