HTTP Content-MD5 首部欄位:編碼的坑

xslidian發表於2014-04-02

在《圖解 HTTP》(上野 宣 著,於均良 譯,人民郵電出版社,2014年4月,ISBN 9787115351531)第129頁(6.6.6節),作者給出了一則首部示例:

Content-MD5: OGFkZDUwNGVhNGY3N2MxMDIwZmQ4NTBmY2IyTY==

和廣為流傳的例子 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== 比較一下,
大~家~有~沒~有~覺~得~有~點~長?

第129頁 第130頁 部分

MD5 作為校驗碼,是一個 128 位長的二進位制數。 在記憶體中,128 bits = 16 octets。 經過 Base64 編碼,長度增加約 33%,編碼後的長度應為 4*⌈16/3⌉ = 24 位元組。 而書中栗子的長度為 40 位元組。

原來這裡也有個坑!

演算法細節

HTTP/1.1(RFC2616#14.15)給出了實體首部欄位 Content-MD5 的語法規則:

Content-MD5   = "Content-MD5" ":" md5-digest
md5-digest   = <base64 of 128 bit MD5 digest as per RFC 1864>

即校驗值的編碼依據為 RFC1864

MD5 演算法輸出的結果為 128 位長的摘要。當以網路位元組序(大端序) 解析時,可得到16位元組的二進位制資料序列。隨後,將這 16 個位元組按 base64 演算法編碼,最終得到可作為 `Content-MD5` 欄位取值的結果。因此,針對 MIME 實體的原始資料應用 MD5 演算法,若得到的摘要值為(幾乎不可能的)"Check Integrity!"注 1,則該 MIME 實體的首部可包含此欄位:
Content-MD5:  Q2hlY2sgSW50ZWdyaXR5IQ==注 2
  1. 這是一個16位元組長的字串,對應的 32 字元表示為 0x436865636b20496e7465677269747921。(筆者查詢了多個逆向庫,均尚未收錄 md5 為該值的資料。)

  2. “Check Integrity!”作為 ASCII 字串的 base64 編碼,長度為 24 位元組。

看吧,RFC1864 給出的例子也是 24 位元組長。 那麼問題出在哪裡?

剛才點選了 Base64 連結的讀者可能已經通過實驗發現答案了:

書中給出的欄位值 OGFkZDUwNGVhNGY3N2MxMDIwZmQ4NTBmY2IyTY== 是對字串 "8add504ea4f77c1020fd850fcb2M"的 base64 編碼,而不是對二進位制數 0x8add504ea4f77c1020fd850fcb2? 的 base64 編碼。

注:實際解碼過程中出現了錯誤(所以出現了比較顯眼的“M”字母)。該 28 位結果是報錯前的部分輸出,但不影響分析問題。

在實踐中,應注意先取得 md5 校驗值的二進位制表示,然後進行 base64 編碼。例如:

  1. PHP 中使用 md5($資料, true) 而非 md5($資料) (via StackOverflow

  2. ColdFusion 中使用 binaryDecode(校驗碼字串, "hex") 再用 binaryEncode(二進位制資料, "base64") (via Ben Nadel

  3. Java 中使用 DigestInputStream.getMessageDigest().digest() 而不是隱式呼叫 DigestInputStream.getMessageDigest().toString() (via Amazon MWS

補充知識

欄位名大小寫

  1. HTTP/1.1(RFC2616#4.2)說了,欄位名不區分大小寫。所以即使寫成 cOnTeNt-mD5 也不會有什麼問題。

  2. 作為 HTTP/2 實驗場的 SPDY 協議(最新草案為 3.2)規定,所有欄位名均必須小寫。

一定要用 base64 編碼嗎?

目前使用 Content-MD5 首部的大流量服務商只有兩家:亞馬遜和百度。(沒錯!) 雖然我把百度列在後面,並不意味著百度雲的流量比亞馬遜雲小。 在網際網路這個世界,的確是誰的嗓門大誰決定行業準則。

亞馬遜嚴格遵守 RFC1864,不僅在下載時提供校驗值,還對上傳檔案的校驗值和請求中的值進行二次核對。

百度雲網盤更貼近民生,直接在響應報文中返回 md5 校驗碼的十六進位制表示(32 位元組),看起來多方便呀!

ETag: b0d95dbfeeb97fa7e411aba81729229f
Content-MD5: b0d95dbfeeb97fa7e411aba81729229f

百度雲網盤部分響應報文

32 只比 24 多 8 個位元組,意義大不一樣。何必死死遵守那個並不是為 HTTP 設計的方案呢?

(如果 Firefox 等保皇派拒絕相容,給欄位名加一個 X- 字首得了。)

相關文章