# 第 9 篇:資料序列化與 CBOR - 為什麼 Apple 不用 JSON? ## 前言:當你第一次看到 App Attest 的回應資料 經過前面五篇文章,我們已經理解了密碼學的核心概念: - **ECDSA** 讓 iPhone 可以產生數位簽章 - **信任鏈** 讓我們相信公鑰的真實性 - **數位簽章** 在各種系統中的應用 現在有個實際問題:當 iPhone 要把這些密碼學資料(公鑰、簽章、憑證)傳給伺服器時,要用什麼格式包裝? 你可能會想:「用 JSON 不就好了嗎?」但當你看到 Apple App Attest 的回應時,會發現它用的是一種叫 **CBOR** 的格式。為什麼? ## 從生活例子理解:資料要怎麼「打包」? ### 📦 寄包裹的類比 想像你要寄一個包裹: **方法一:用紙箱包裝(像 JSON)** - ✅ **看得懂**:打開就知道裡面是什麼 - ✅ **好處理**:任何人都會用紙箱 - ❌ **占空間**:紙箱本身很大 - ❌ **不適合特殊物品**:易碎品、液體需要特殊處理 **方法二:用真空包裝(像 CBOR)** - ✅ **省空間**:壓縮後體積小很多 - ✅ **保護性好**:適合精密物品 - ❌ **打開麻煩**:需要專門工具 - ❌ **看不懂**:外觀看不出內容 ### 💻 數位世界的「打包」問題 在數位世界裡,把資料打包起來的過程叫做「**序列化**」。 **為什麼需要序列化?** ``` 你的 iPhone 記憶體裡: ├── 公鑰(一堆數字) ├── 簽章(一堆數字) └── 其他資訊(文字、數字) 要傳給伺服器 → 需要「打包」成一串資料 → 伺服器「解包」還原 ``` 就像寄包裹一樣,你需要: 1. **打包**:把東西裝進箱子 2. **運送**:透過網路傳輸 3. **解包**:對方收到後拆開 ## 常見的「打包」方式比較 ### 📄 JSON:最常見的格式 **就像用透明塑膠袋包裝,看得清楚** ```json { "name": "John", "age": 30, "publicKey": "LS0tLS1CRUdJTi..." } ``` **優點:** - ✅ 人類可讀:打開就看得懂 - ✅ 到處都支援:所有程式語言都會處理 - ✅ 容易除錯:出問題可以直接看 **缺點:** - ❌ 體積大:很多重複的引號、括號 - ❌ 二進位資料麻煩:需要先轉成文字(Base64) - ❌ 傳輸慢:資料大,傳得久 **實際例子:** ``` 原始資料:32 bytes 的二進位資料 JSON 表示:需要約 50 bytes(因為要用 Base64 編碼) ``` ### 🎁 CBOR:Apple 的選擇 **就像用真空壓縮袋,體積小、保護好** **優點:** - ✅ 體積小:同樣的資料,比 JSON 小 20-40% - ✅ 二進位友善:可以直接放入二進位資料 - ✅ 解析快:電腦處理更快 - ✅ 適合手機:省電、省流量 **缺點:** - ❌ 人類不可讀:看起來像亂碼 - ❌ 除錯麻煩:需要專門工具 **實際例子:** ``` 原始資料:32 bytes 的二進位資料 CBOR 表示:約 35 bytes(直接包裝,幾乎沒有額外負擔) JSON 表示:約 50 bytes(需要 Base64) 省下約 30% 的空間! ``` ## 為什麼 Apple App Attest 選擇 CBOR? ### 🎯 三個關鍵原因 #### 1. 省空間、省流量 App Attest 的回應包含: - 公鑰(64 bytes) - 數位憑證(可能幾 KB) - 簽章(64 bytes) - 其他資料 **如果用 JSON:** - 所有二進位資料都要 Base64 編碼 - 增加 33% 體積 - 手機多用流量、多耗電 **如果用 CBOR:** - 直接包裝二進位資料 - 體積最小 - 省電省流量 #### 2. 更適合密碼學資料 密碼學資料特點: - ✅ 大部分是二進位(公鑰、簽章、憑證) - ✅ 精確性要求高(一個 bit 錯都不行) - ✅ 不需要人類閱讀 CBOR 完全符合這些需求! #### 3. 業界標準 CBOR 不是 Apple 發明的,而是 **IETF RFC 標準**(就像 HTTP、JSON 一樣)。 使用 CBOR 的系統: - 🍎 Apple App Attest - 🌐 WebAuthn(網站登入標準) - 🔐 FIDO2(身份驗證標準) ## CBOR 基本概念:簡單的編碼結構 ### 🏗️ CBOR 的結構:類型 + 資料 CBOR 的核心設計很簡單:**每個資料都帶著自己的「標籤」** ``` CBOR 編碼結構: ┌─────────┬─────────┐ │ 類型 │ 資料 │ │ (Tag) │ (Value) │ └─────────┴─────────┘ 就像貨物標籤: 📦 「易碎品」標籤 → 裡面是玻璃杯 📦 「冷藏」標籤 → 裡面是食物 📦 「數字」標籤 → 裡面是整數 ``` ### 🔢 CBOR 的基本類型 | 類型 | Tag 開頭 | 說明 | 例子 | |------|---------|------|------| | **整數** | 0x00-0x1B | 正整數 | `42` | | **文字** | 0x60-0x7B | UTF-8 字串 | `"hello"` | | **二進位** | 0x40-0x5B | 位元組串 | 公鑰、簽章 | | **陣列** | 0x80-0x9B | 有序列表 | `[1, 2, 3]` | | **Map** | 0xA0-0xBB | 鍵值對 | `{"name": "John"}` | ### 📝 簡單範例:編碼一個數字 **編碼數字 42:** ``` 原始資料:42 CBOR 編碼:18 2a │ └─ 數值:42(十六進位 0x2a) └──── 類型:正整數 只需要 2 bytes! ``` **編碼字串 "hello":** ``` 原始資料:"hello" CBOR 編碼:65 68 65 6c 6c 6f │ └─────┬────────┘ │ └─ "hello" 的 UTF-8 編碼 └──────── 類型:文字(長度 5) 需要 6 bytes(1 byte 類型 + 5 bytes 資料) ``` **編碼 Map(字典):** ``` 原始資料:{"age": 30} CBOR 編碼:a1 63 61 67 65 18 1e │ │ └───┬────┘ │ └─ 數值:30 │ │ │ └──── 類型:整數 │ │ └─────────── "age" 的 UTF-8 │ └────────────────── 類型:文字(長度 3) └───────────────────── 類型:Map(1 個鍵值對) 需要 7 bytes ``` ### 💡 為什麼這樣設計? **對比 JSON:** ``` JSON: {"age": 30} 需要:12 bytes(包含引號、冒號、括號) CBOR: a1 63 61 67 65 18 1e 需要:7 bytes 省下 42% 空間! ``` **關鍵差異:** - JSON:需要很多「裝飾」字元(`{`, `}`, `"`, `:` 等) - CBOR:用一個 byte 的標籤取代所有裝飾 ### 🎁 CBOR 適合什麼資料? ``` 適合: ├── 二進位資料(公鑰、簽章、憑證) │ └── 直接放入,不需要 Base64 ├── 結構化資料(有層次的資訊) │ └── Map、Array 可以嵌套 └── 需要省空間的場景 └── 行動裝置、IoT 不適合: └── 需要人類直接閱讀的資料 └── 用 JSON 更好 ``` ### 📊 JSON vs CBOR 實際對比 **場景:iPhone 傳送公鑰給伺服器** **使用 JSON:** ```json { "publicKey": { "x": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0=", "y": "TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR" } } ``` - 大小:約 120 bytes - 可讀性:✅ 人類可讀 - 效率:❌ Base64 編碼增加 33% 體積 **使用 CBOR:** ``` a1 66 70 75 62 6c 69 63 4b 65 79 a2 61 78 58 20 [32 bytes 原始資料] 61 79 58 20 [32 bytes 原始資料] ``` - 大小:約 80 bytes - 可讀性:❌ 人類不可讀 - 效率:✅ 直接儲存,省 40 bytes **省下 33% 的空間!** ## App Attest 中的 CBOR:實際應用 ### 🍎 Attestation Object 的結構 當 iPhone 完成 attestation,會回傳一個 CBOR 物件: ``` Attestation Object(CBOR 格式)- 三個主要欄位 ├── fmt(格式): "apple-appattest" ├── attStmt(證明聲明) │ ├── x5c: [憑證1, 憑證2](都是二進位) │ └── receipt: App Store 收據(二進位) └── authData(認證資料): 二進位資料(包含公鑰、簽章計數等) ``` **實際解碼後的樣子:** ```javascript { fmt: 'apple-appattest', attStmt: { x5c: [ <Buffer 30 82 02 cc ...>, // 憑證1(二進位) <Buffer 30 82 02 36 ...> // 憑證2(二進位) ], receipt: <Buffer 30 80 06 09 ...> // App Store 收據(二進位) }, authData: <Buffer 21 c9 9e 00 ...> // 認證資料(二進位) } ``` **為什麼用 CBOR?** - ✅ 憑證是二進位(DER 格式)→ CBOR 直接放 - ✅ 收據是二進位 → CBOR 直接放 - ✅ authData 包含公鑰等資料,也是二進位 → CBOR 直接放 **如果用 JSON 會怎樣?** ```json { "fmt": "apple-appattest", "attStmt": { "x5c": [ "MIICzDCCAbSgAwIBAgI...", // Base64 編碼,變長了! "MIIC2jCCAcKgAwIBAgI..." // Base64 編碼,變長了! ], "receipt": "MIAGCSqGSIb3DQE..." // Base64 編碼,變長了! }, "authData": "IcmeAEgk8y2+6..." // Base64 編碼,變長了! } ``` 所有二進位資料都要 Base64 編碼,體積增加 33%! ### 🔍 你不需要手動解析 CBOR **重點:你通常不需要自己寫 CBOR 解析器!** 各種程式語言都有現成的函式庫: **Python:** ```python import cbor2 # 解碼 CBOR data = cbor2.loads(cbor_bytes) print(data['fmt']) # 輸出:apple-appattest ``` **PHP:** ```php use CBOR\Decoder; use CBOR\StringStream; $decoder = new Decoder(); // 解析 CBOR $parsed = $decoder->decode(new StringStream($cborBytes)); // 轉成 PHP array $data = $parsed->normalize(); echo $data['fmt']; // 輸出:apple-appattest ``` **JavaScript:** ```javascript const cbor = require('cbor'); const data = cbor.decodeFirstSync(cborBuffer); console.log(data.fmt); // 輸出:apple-appattest ``` ## 與其他格式的比較 ### 📊 三種格式的應用場景 | 格式 | 適用場景 | 優勢 | Apple 生態應用 | |------|---------|------|---------------| | **JSON** | API 回應、設定檔 | 人類可讀、除錯容易 | 一般 API、設定 | | **CBOR** | 密碼學資料、二進位資料 | 體積小、效率高 | **App Attest** | | **XML** | 舊系統、文件格式 | 擴展性好、驗證嚴格 | 較少使用 | ### 🎯 什麼時候該用哪種格式? **使用 JSON 的時機:** - ✅ 一般的 Web API(大部分情況) - ✅ 需要人類閱讀的設定檔 - ✅ 除錯方便很重要 **使用 CBOR 的時機:** - ✅ 傳輸二進位資料(憑證、簽章) - ✅ 行動裝置(省電、省流量) - ✅ 密碼學應用(App Attest、WebAuthn) ## 實務開發建議 ### 💡 你需要知道的重點 #### 1. 理解概念就夠了 你不需要: - ❌ 記憶 CBOR 的每個位元組格式 - ❌ 手動計算 CBOR 編碼 - ❌ 深入研究 CBOR 規格 你只需要: - ✅ 知道 CBOR 是什麼、為什麼用 - ✅ 會使用函式庫解碼 CBOR - ✅ 理解 App Attest 用 CBOR 的原因 #### 2. 使用現成工具 **除錯 CBOR:** - 線上工具:[cbor.me](http://cbor.me) - 命令列工具:`cbor2diag` - 程式庫自帶的除錯功能 **範例:除錯 CBOR 資料** ```python import cbor2 import json # 讀取 CBOR with open('attestation.cbor', 'rb') as f: data = cbor2.load(f) # 轉成 JSON 方便查看(二進位資料會變成 Base64) print(json.dumps(data, indent=2, default=str)) ``` #### 3. 常見錯誤處理 **問題:CBOR 解碼失敗** ```python try: data = cbor2.loads(cbor_bytes) except Exception as e: print(f"CBOR 解碼失敗:{e}") # 檢查資料是否完整 print(f"資料長度:{len(cbor_bytes)} bytes") # 檢查前幾個位元組 print(f"開頭:{cbor_bytes[:10].hex()}") ``` **常見原因:** - ❌ 資料不完整(網路傳輸中斷) - ❌ Base64 解碼錯誤(如果先經過 Base64) - ❌ 格式不是 CBOR(可能是其他格式) ## 本文小結:資料格式的選擇哲學 ✅ **JSON 的價值**:人類可讀、除錯容易、適合一般用途 ✅ **CBOR 的價值**:體積小、效率高、適合密碼學資料 ✅ **格式選擇**:根據使用場景選擇最適合的格式 ✅ **實務開發**:使用現成函式庫,理解概念比細節重要 ### 🎓 與前面文章的連結 - **第 3 篇(ECDSA)**:公鑰和簽章都是二進位資料 → 用 CBOR 最適合 - **第 4 篇(憑證鏈)**:憑證也是二進位(DER 格式)→ 用 CBOR 直接包裝 - **第 5 篇(數位簽章應用)**:App Attest 的所有密碼學資料都用 CBOR 傳輸 ## 下一步學習 下一篇《第10篇:X.509 憑證格式》會帶你理解: - 憑證裡面裝了什麼? - 為什麼憑證看起來像亂碼? - App Attest 的憑證有什麼特別的地方? 你會發現,憑證本身也是一種「打包」格式,就像 CBOR 一樣! --- ## 💡 補充資料 ### 常見問題 **Q: 我一定要學會 CBOR 嗎?** A: 不用!使用現成函式庫就可以了。理解「為什麼用 CBOR」比「怎麼編碼 CBOR」更重要。 **Q: CBOR 比 JSON 好嗎?** A: 沒有絕對好壞,看使用場景。JSON 適合人類閱讀,CBOR 適合機器處理。 **Q: 如果我想看 CBOR 的內容怎麼辦?** A: 用程式庫解碼成 JSON 或 Python dict,就可以看懂了。 ### 進階資源 - [CBOR 官方網站](https://cbor.io) - [RFC 8949 - CBOR 規格](https://www.rfc-editor.org/rfc/rfc8949.html)(給有興趣的人) - [cbor.me](http://cbor.me) - 線上 CBOR 除錯工具