ByteScope

PDF 檔案格式

Portable Document Format,Adobe 的固定版面文件標準。

MIME 類型
application/pdf
副檔名
.pdf
分類
文件

Magic number(檔案簽章)

0x00000000255044462D%PDF-'%PDF-' + version

關於 PDF 格式

PDF 是一張由編號物件組成的圖——字典、陣列、串流、數字與字串——加上一份交叉參照表,把每個物件編號對應到它在檔案裡的位元組位移。這個設計解釋了為什麼閱讀器打開 500 頁的 PDF 是瞬間的事:它只讀最後幾百個位元組,順著 startxref 找到交叉參照表,然後直接跳到需要的物件。這也解釋了為什麼 PDF 對「往後追加」異常寬容,對「任何會位移位元組的編輯」異常脆弱,因為表裡每個位移都是絕對值。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
05%PDF- header marker5 個 ASCII 位元組 25 50 44 46 2D。PDF 在最上層是偏文字的格式,所以這其實就是一行 PostScript 風格的註解;百分比符號是註解字元,整個第一行對完全不懂 PDF 的解析器來說也是合法的。
53Version numberISO 32000-1 與更早的 Adobe 版本是 '1.0' 到 '1.7',ISO 32000-2 是 '2.0'。這裡的版本是下限而不是事實:文件 catalog 可以帶一個 /Version 鍵往上覆蓋,所以標成 1.4 的檔案合法地使用 1.7 的功能是可能的。
81Header end-of-line一個 LF(0A)、一個 CR(0D),或是 CR LF 這一對——是後者的話這段就是兩個位元組,底下所有東西跟著往後移一位。標頭這一行不准放其他東西。
9variableBinary comment lineISO 32000-1 第 7.5.2 條建議,含有二進位資料的檔案應該在標頭後面接一行註解,裡面至少四個位元組的碼值大於等於 128。它存在的唯一目的,是讓傳輸軟體把這個檔案歸類成二進位而不是文字。常見的產生器會寫 %âãÏÓ;沒有這一行仍然合法,但等於告訴你寫檔的人不太講究。
-55%%EOF marker格式正確的檔案,最後一行是 5 個位元組 25 25 45 4F 46。後面可能還跟著換行,所以讀取端是從最後 1024 個位元組往回掃,而不是相信固定位置——另外,檔案裡出現好幾個 %%EOF 不是壞掉,而是它被增量更新過。
variablevariablestartxref and the trailer%%EOF 前面緊接著關鍵字 'startxref',下一行是一個十進位的位元組位移。讀取端會跳到那個位移去找交叉參照表或交叉參照串流,再由它找到 /Root、/Info 和 /Encrypt 這些項目。這裡沒有任何東西位在固定位置,這正是檔尾那個標記非得用掃描找出來的原因。

怎麼跟長得像的格式分開

容易搞混的怎麼分
PDF/APDF/A 是長期保存用的規範,PDF/X 是送印交換用的,兩者在位移 0 都不會留下任何痕跡——符合性等級是中繼資料,不是簽章。PDF/A 檔案是在 XMP 中繼資料串流裡透過 pdfaid 命名空間宣告自己的 part 與 conformance level,另外還必須內嵌所有字型並帶上 output intent。所以一個檔案可以在 XMP 裡自稱 PDF/A,卻通不過驗證;標頭無論如何都給不了你答案。
Encrypted PDF開頭也是一模一樣。線索是檔尾 trailer 字典裡的 /Encrypt 鍵。要注意 PDF 加密在實務上通常代表什麼:只管權限的「擁有者密碼」,使用者密碼是空的,所以內容完全讀得到,禁止列印或複製全靠閱讀器自己守規矩。那和使用者密碼是兩回事,後者才真的會把串流加密。
OLE2 / CFB有人說「文件寄給我」的時候,你會遇到的就是這兩種。2007 年以前的 Word 檔是 Compound File Binary 容器,開頭 D0 CF 11 E0;PDF 開頭是 %PDF-。真正影響後續的差別是 PDF 把字元位置釘死在頁面上,.doc 沒有,所以少了字型的機器打開 .doc 會重排,PDF 不會。
PostScriptPDF 的直系祖先,到現在還會被搞混,因為兩者用同一套物件語法、同一個註解字元。PostScript 檔案開頭是 '%!PS-Adobe-';EPS 開頭一樣,前面還可能多一段二進位預覽標頭 C5 D0 D3 C6。PostScript 是一整套從上到下執行的程式語言,PDF 是可隨機存取的靜態物件圖——PDF 之所以取代它,原因就在這裡。

常見的壞法

  • 會位移位元組的編輯。因為交叉參照表存的是絕對位元組位移,在檔案任何地方插入或刪掉一個位元組,後面每一筆都失效。閱讀器的應對方式是重建整張表——掃過整個檔案找 'N 0 obj' 這種樣式——所以手改過的 PDF 打開時會慢一下、跳個警告,而不是乾淨俐落地載入。
  • 下載被截斷。靠前的物件還在,所以重建之後可能還算繪得出一兩頁,但檔尾的交叉參照表和 trailer 沒了,閱讀器根本沒有起點。檢查方式是看最後 1024 個位元組裡有沒有 %%EOF。
  • 文字模式傳輸。FTP 開在 ASCII 模式會改寫 FlateDecode 壓縮串流裡的 0D 0A。標頭沒被動到、檔案照樣解析得動,所以打得開——然後個別頁面一片空白或丟出串流解碼錯誤,因為那些 deflate 資料再也 inflate 不回來了。
  • 增量更新被接在錯的基底上。PDF 支援用追加的方式記錄變更而不必整份重寫,所以簽過名或加過註解的檔案,是原檔加上第二段指回前一段的交叉參照。如果接上去那一段的 /Prev 位移和前一個 %%EOF 的實際位置對不上,整條鏈就斷了;而檔案的標頭和最後的 trailer 看起來都還好好的。
  • 產生器寫了交叉參照串流,卻宣稱版本是 1.4。壓縮的交叉參照串流與物件串流是 PDF 1.5 才有的。老實照著宣告版本走的讀取端根本不會去找它們,於是文件看起來一個物件都沒有,儘管每個位元組都在、都正確。

這個格式現在還在用嗎

只要文件的外觀不能跑掉,PDF 就是唯一被廣泛接受的格式,三十年下來沒有東西取代得了它這個角色。合約、發票、對帳單、論文、表單、送印稿,以及任何需要歸檔或簽署的東西,最後都變成 PDF,因為它會內嵌字型、把每個字元的位置釘死,不會為了配合閱讀器而重排。它不適合的場合是「之後還要再編輯」——PDF 存的是繪圖指令而不是段落,所謂「編輯 PDF」等於把一段段文字逆推回句子——以及在手機上閱讀,固定的 A4 頁面在小螢幕上正好是最糟的形狀,能重排的 EPUB 完勝。它的專門變體各自佔住了穩固的位置:PDF/A 用於長期典藏,要求所有字型都必須內嵌、不得依賴外部資源;PDF/X 用於送印交件。這個格式現在是 ISO 標準而不是 Adobe 的私產,瀏覽器也各自內建了算繪引擎,所以 PDF 是極少數在任何裝置上不用裝東西就打得開的檔案類型。

在這個站上打開 PDF 檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 PDF 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
OLE2 / CFBD0 CF 11 E0 A1 B1.doc
RTF7B 5C 72 74 66 31.rtf
Word 文件(docx)50 4B 03 04.docx

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 PDF?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。