在電腦通訊的早期歷史中,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–25 | A–Z |
| 26–51 | a–z |
| 52–61 | 0–9 |
| 62 | + |
| 63 | / |
本章回顧#
本章定義了在二進位與文字協定中表示資料值的多種方式,並討論了如何在二進位中表示整數這類數值資料。
- 理解八位元組如何在協定中傳輸,是成功解碼數值的關鍵所在:位元組序處理錯誤,後面全盤皆錯。
- 辨識變長資料值的各種表示方式同樣重要,因為它們大概是你在網路協定中會遇到最重要的結構。
- 無論二進位或文字協定,結構化格式(ASN.1/DER、MIME、JSON、XML)與編碼方式(hex、percent、Base64)都是可重複辨認的既有設計,不需要每次從頭猜。
隨著你分析愈來愈多網路協定,會看到同樣的結構反覆出現。能夠快速指認這些結構,正是輕鬆處理未知協定的關鍵。
接下來,我們會檢視幾個真實世界的協定,把它們拆解開來,看看它們如何對應到本章描述的這些結構。