在電腦通訊的早期歷史中,8 位元的位元組並非常態。因為多數通訊是文字為主、且以英語系國家為中心,每個位元組只送 ASCII 標準所需的 7 個位元在經濟上是合理的——剩下的位元可以拿來控制序列鏈路協定或提升效能。這段歷史深深反映在一些早期網路協定上,例如 SMTP 與網路新聞傳輸協定(Network News Transfer Protocol, NNTP)都假設通道是 7 位元的。

但 7 位元限制帶來問題:你想用電子郵件寄那張好笑的圖片給朋友,或想用非英文字元集寫信,該怎麼辦?為了突破這個限制,開發者設計出多種把二進位資料編碼成文字的方法,各自有不同的效率與複雜度。

把二進位內容轉成文字直到今天仍有價值。例如你想在 JSON 或 XML 這類結構化文字格式中傳送二進位資料,就得確保分隔符被適當跳脫;改用 Base64 這種現成編碼格式,兩端都能輕鬆理解。

十六進位編碼#

十六進位編碼(hex encoding)是最天真的二進位資料編碼技巧之一:把每個八位元組拆成兩個 4 位元值,各自轉換成代表其十六進位表示的文字字元。結果就是二進位的簡單文字形式。

原始位元組:  06 E3 58
Hex 編碼後:  "06E358"

圖表 3-18:二進位資料的十六進位編碼範例

面向評價
空間效率差——所有二進位資料一律變成原本的 100% 大小
編解碼速度快速且簡單
安全性幾乎不會出錯,從安全角度來看確實有好處

HTTP 為 URL 與部分文字協定指定了一種類似的編碼,稱為百分比編碼(percent encoding)。它不是把所有資料都編碼,而是只把不可列印的資料轉成十六進位,並在值前面加上 % 字元標示。

原始位元組:      06 E3 58
百分比編碼後:    "%06%E3%58"

Base64#

為了對付十六進位編碼明顯的低效率,我們可以改用 Base64 這種編碼方案,它最初是作為 MIME 規格的一部分而開發的。名稱中的 64 指的是用來編碼資料的字元數量。

輸入的二進位被切成一個個 6 位元值,足以表示 0 到 63,接著用這個值查編碼表對應到一個字元。

圖表 3-19:Base64 編碼表

三對四的補位設計#

這個做法有個問題:8 位元除以 6 位元會餘下 2 位元。解法是以三個八位元組為單位取用輸入,因為 24 位元除以 6 位元剛好得到 4 個值。

圖表 3-20:Base64 把 3 個位元組編碼成 4 個字元

因此 Base64 把 3 個位元組編碼成 4 個字元,只增加 33%,遠優於十六進位編碼帶來的膨脹。

佔位字元#

還有另一個問題:如果只剩一或兩個八位元組要編碼怎麼辦?Base64 用定義一個佔位字元等號 = 來解決。編碼過程中若沒有有效位元可用,編碼器就把該值編成佔位字元。

剩餘輸入產生的 = 佔位字元數
1 個八位元組2 個
2 個八位元組1 個
3 個八位元組0 個

圖表 3-21:Base64 把 1 個位元組編碼成 3 個字元

要把 Base64 資料轉回二進位,只要反向執行這些步驟即可。

但解碼過程中若遇到非 Base64 字元會怎樣?那就由應用程式自行決定了。我們只能希望它做出安全的決定——這正是值得測試的地方。

參考:Base64 編碼表

6 位元值與字元的對應關係:

值範圍對應字元
0–25AZ
26–51az
52–6109
62+
63/

本章回顧#

本章定義了在二進位與文字協定中表示資料值的多種方式,並討論了如何在二進位中表示整數這類數值資料。

  • 理解八位元組如何在協定中傳輸,是成功解碼數值的關鍵所在:位元組序處理錯誤,後面全盤皆錯。
  • 辨識變長資料值的各種表示方式同樣重要,因為它們大概是你在網路協定中會遇到最重要的結構。
  • 無論二進位或文字協定,結構化格式(ASN.1/DER、MIME、JSON、XML)與編碼方式(hex、percent、Base64)都是可重複辨認的既有設計,不需要每次從頭猜。

隨著你分析愈來愈多網路協定,會看到同樣的結構反覆出現。能夠快速指認這些結構,正是輕鬆處理未知協定的關鍵。

接下來,我們會檢視幾個真實世界的協定,把它們拆解開來,看看它們如何對應到本章描述的這些結構。