---
# System prepended metadata

title: '第 12 篇: X9.62 橢圓曲線公鑰格式'
tags: [AppCheck系列]

---

# 第 12 篇: X9.62 橢圓曲線公鑰格式

## 前言: 從 App Attest 公鑰提取說起

在實作 Apple App Attest 時,你會遇到橢圓曲線公鑰。這些公鑰需要用標準格式表示和傳輸，**X9.62** 就是定義橢圓曲線點（公鑰）編碼方式的標準。

**本文重點：**
- X9.62 格式：橢圓曲線點如何表示成二進位（`04 || X || Y`）
- SPKI 結構：X9.62 點如何包裝成完整的公鑰（加上演算法資訊）
- 在 App Attest 中的應用：憑證裡的公鑰如何組織

## 什麼是 X9.62?

**X9.62** 是 ANSI(美國國家標準協會)制定的橢圓曲線密碼學標準,定義了:
- 橢圓曲線參數
- 公鑰/私鑰格式
- 數位簽章演算法

**本文重點**: X9.62 定義的**橢圓曲線點(公鑰)編碼格式**

## 橢圓曲線上的點

### 數學基礎回顧

橢圓曲線上的一個點由兩個座標組成:

```
點 P = (X, Y)

其中:
- X 是橫座標
- Y 是縱座標
- 兩者都是有限域上的大整數
```

**P-256 曲線為例:**
- X: 256 bits = 32 bytes
- Y: 256 bits = 32 bytes

### 為什麼需要編碼格式?

橢圓曲線公鑰本質上就是曲線上的一個點 (X, Y),但:
1. 如何將這個點儲存成二進位資料?
2. 如何在網路上傳輸?
3. 如何讓不同系統互通?

**X9.62 標準定義了統一的編碼方式**

## X9.62 點格式詳解

### 1. 未壓縮格式 (Uncompressed)

這是最直觀的格式:

```
格式: 0x04 || X || Y

結構:
├── 0x04: 1 byte (標記為「未壓縮」)
├── X: 32 bytes (橫座標)
└── Y: 32 bytes (縱座標)

總長度: 65 bytes (P-256)
```

**範例 (P-256):**
```
04 [32 bytes X] [32 bytes Y]
│  └─────────┬──────────┘ └─────────┬──────────┘
│            X 座標                  Y 座標
未壓縮標記
```

**實際資料:**
```
04
1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b  (X)
9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b  (Y)
```

### 2. 壓縮格式 (Compressed)

由於橢圓曲線方程式的對稱性,給定 X 座標,Y 座標只有兩個可能值(正負)。

```
格式: 0x02/0x03 || X

結構:
├── 0x02 或 0x03: 1 byte
│   ├── 0x02: Y 是偶數
│   └── 0x03: Y 是奇數
└── X: 32 bytes (橫座標)

總長度: 33 bytes (P-256)
```

**節省空間:**
- 未壓縮: 65 bytes
- 壓縮: 33 bytes
- 節省: 49%

**為什麼 App Attest 用未壓縮格式?**
- 相容性更好
- 處理更簡單
- 空間差異不大(只有 32 bytes)

### 3. 特殊情況: 無限遠點

```
格式: 0x00

這代表橢圓曲線的「無限遠點」(identity element)
在實務中很少用到
```

## X9.62 在 SPKI 中的位置

X9.62 點通常不會單獨使用，而是被包裝在 **SPKI（SubjectPublicKeyInfo）結構**中：

```
SubjectPublicKeyInfo (SPKI) - 完整的公鑰格式
├── AlgorithmIdentifier
│   ├── algorithm: id-ecPublicKey (1.2.840.10045.2.1)
│   └── parameters: secp256r1 (1.2.840.10045.3.1.7)
└── subjectPublicKey: BIT STRING
    └── 04 || X || Y  ← X9.62 點在這裡
```

**關鍵理解：**
- X9.62 = 只有點座標（65 bytes）
- SPKI = X9.62 + 演算法資訊（~91 bytes）
- 憑證裡儲存 SPKI DER，函式庫通常輸出 SPKI PEM

**實務上：**從憑證提取公鑰得到的是完整 SPKI（`-----BEGIN PUBLIC KEY-----`），可直接使用。詳見「實務考量」章節。

## 從 X9.62 建構 SPKI（罕見情境）

**如果**你只有 X9.62 原始點資料（例如從其他來源獲得），才需要手動建構 SPKI：

```
步驟 1: 準備 X9.62 點資料
├── 已有: 04 || X || Y (65 bytes)
└── 確認格式正確

步驟 2: 建構 AlgorithmIdentifier
├── algorithm OID: 1.2.840.10045.2.1 (id-ecPublicKey)
└── parameters OID: 1.2.840.10045.3.1.7 (secp256r1/P-256)

步驟 3: 建構 SPKI 結構
├── algorithmIdentifier (步驟 2)
└── subjectPublicKey (BIT STRING 包裝 X9.62 點)

步驟 4: DER 編碼整個 SPKI

步驟 5: (可選) PEM 編碼
└── Base64(DER) + 頭尾標記
```

**結果:**
```
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
```

### 關鍵 OID 說明

**算法 OID: 1.2.840.10045.2.1**
```
1 (ISO)
└── 2 (ISO member body)
    └── 840 (美國)
        └── 10045 (ANSI X9.62)
            └── 2 (publicKeyType)
                └── 1 (id-ecPublicKey)
```

**曲線 OID: 1.2.840.10045.3.1.7**
```
1.2.840.10045 (ANSI X9.62)
└── 3 (curves)
    └── 1 (prime curves)
        └── 7 (secp256r1 / P-256)
```

## 在 App Attest 中的應用

**憑證中的位置：**
```
CBOR Attestation Object
└── attStmt.x5c[0] (憑證)
    └── subjectPublicKeyInfo (SPKI)
        └── subjectPublicKey
            └── 04 || X || Y  ← X9.62 點
```

**實務處理：**
1. 從憑證提取 SPKI（函式庫自動處理）
2. 儲存為 PEM 供後續使用
3. 驗證簽章時直接使用（函式庫會提取內部的 X9.62）

## 實務考量

### 1. 從憑證提取公鑰的正確理解

**常見誤解：**  
❌ 從憑證提取的是 X9.62 點資料，需要轉換成 SPKI  
✅ 從憑證提取的就是 SPKI 結構，其內部包含 X9.62 點  

**為什麼密碼學函式庫要求 SPKI 格式？**
- SPKI 包含完整資訊：演算法（EC）+ 曲線參數（P-256）+ 點資料（X9.62）
- X9.62 只有點座標，缺少曲線資訊
- 函式庫需要知道用哪條曲線才能驗證簽章

**⚠️ 特別注意：PHP phpseclib 的行為**

如果你用 PHP phpseclib 解析憑證，會看到：
```php
$cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
= "-----BEGIN PUBLIC KEY-----\nMFkw...\n-----END PUBLIC KEY-----"
```

**❓ 為什麼會看到 PEM 格式？這是哪一層的資料？**

**答案：這是函式庫的「便利功能」**

```
憑證 DER 裡實際儲存的：
subjectPublicKeyInfo (SPKI 結構，DER 編碼)
├── algorithm (DER)
└── subjectPublicKey (BIT STRING, DER)
    └── 04 || X || Y  ← X9.62 raw bytes

phpseclib 處理後輸出：
['subjectPublicKeyInfo']
├── ['algorithm'] ✅ 保持原樣
└── ['subjectPublicKey'] ⚠️ 被轉換了！
    └── "-----BEGIN PUBLIC KEY-----..."
        ↑ 這是把整個 SPKI 重新編碼成 PEM！
```

**為什麼會這樣？**
1. phpseclib 讀取憑證中的 SPKI DER 資料
2. 為了方便使用者，自動轉成 PEM 格式
3. 把 PEM 字串放在 `['subjectPublicKey']` key 下
4. 雖然 key 名稱是 `subjectPublicKey`，但內容是「整個 SPKI 的 PEM」

**這容易造成的誤解：**
- ❌ 誤解：「這個 key 應該是 BIT STRING 的內容（X9.62 點）」
- ✅ 真相：「這是整個 SPKI 結構，被函式庫轉成 PEM 格式了」

**實際對應關係：**
```
ASN.1 結構：
subjectPublicKeyInfo (SPKI)
├── algorithm
└── subjectPublicKey (BIT STRING) ← 這裡才是 X9.62 raw bytes

PHP phpseclib 解析結果：
['subjectPublicKeyInfo']
├── ['algorithm']
└── ['subjectPublicKey'] ← 這裡是整個 SPKI 的 PEM！不是 raw bytes！
```

**總結：**
- 你從 phpseclib 拿到的 `subjectPublicKey` 值已經是完整可用的 SPKI PEM
- 可以直接用於驗證簽章
- 如果要提取內部的 X9.62 raw bytes，需要再解析這個 PEM



### 3. 如何查看 SPKI 內部的 X9.62 點

如果你想「看到」SPKI PEM 內部的 X9.62 點資料，可以使用：

**OpenSSL 工具：**
```bash
openssl ec -pubin -in pubkey.pem -text -noout
# 輸出會顯示：pub: 04:1a:2b:3c...（X9.62 點）
```

**概念理解：**
- SPKI PEM 是完整可用的公鑰
- X9.62 點在其內部（BIT STRING 欄位）
- 大部分情況不需要提取，直接使用 SPKI 即可

## 與其他格式的對比

### X9.62 vs SPKI - 包含關係，不是轉換關係

**關鍵理解：SPKI 包含 X9.62**

```
你看到的 PEM 公鑰檔案：
┌─────────────────────────────────────────┐
│ -----BEGIN PUBLIC KEY-----              │ ← 這整個是 SPKI
│ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE│
│ ...                                     │
│ -----END PUBLIC KEY-----                │
└─────────────────────────────────────────┘
         │ Base64 解碼
         ▼
SPKI (DER 格式，~91 bytes for P-256)
├── AlgorithmIdentifier (演算法資訊)
│   ├── algorithm: id-ecPublicKey
│   └── parameters: secp256r1 (P-256)
└── subjectPublicKey (BIT STRING)
    └── X9.62 點資料 (65 bytes) ← 在這裡！
        ├── 0x04 (未壓縮標記)
        ├── X 座標 (32 bytes)
        └── Y 座標 (32 bytes)
```

**對比表：**

| 特徵 | X9.62 點 | SPKI 結構 |
|------|---------|-----------|
| **內容** | 只有點座標（X, Y） | 演算法 + 曲線參數 + X9.62 點 |
| **大小** | 65 bytes (P-256 未壓縮) | ~91 bytes (DER) |
| **包含曲線資訊** | ❌ | ✅ |
| **能直接驗證簽章** | ❌（缺少曲線資訊） | ✅ |
| **你從憑證提取到的** | ❌ | ✅ 就是這個！ |
| **PEM 格式標頭** | (沒有獨立 PEM) | `-----BEGIN PUBLIC KEY-----` |
| **使用場景** | 嵌入在 SPKI 內部 | 完整的公鑰檔案 |

**重點：**
- ❌ **錯誤理解**：從憑證提取 X9.62，需要轉換成 SPKI
- ✅ **正確理解**：從憑證提取的就是 SPKI，X9.62 在其內部

### X9.62 vs JWK (JSON Web Key)

```json
JWK 格式範例:
{
  "kty": "EC",
  "crv": "P-256",
  "x": "base64url(X)",
  "y": "base64url(Y)"
}
```

**對比:**
- X9.62: 二進位緊湊格式
- JWK: JSON 文字格式
- 都表示同一個點,只是編碼不同

## 其他橢圓曲線的 X9.62 格式

### P-384 (secp384r1)

```
未壓縮格式:
├── 0x04: 1 byte
├── X: 48 bytes
└── Y: 48 bytes
總長: 97 bytes

壓縮格式:
├── 0x02/0x03: 1 byte
└── X: 48 bytes
總長: 49 bytes
```

### P-521 (secp521r1)

```
未壓縮格式:
├── 0x04: 1 byte
├── X: 66 bytes
└── Y: 66 bytes
總長: 133 bytes

壓縮格式:
├── 0x02/0x03: 1 byte
└── X: 66 bytes
總長: 67 bytes
```

**注意:** P-521 是 521 bits,不是 512 bits (66 bytes = 528 bits,但實際只用 521)

## 安全性考量

### 1. 點驗證的重要性

**必須驗證點在曲線上:**
```
給定點 (X, Y),驗證:
y² ≡ x³ + ax + b (mod p)

如果不驗證:
- 可能接受無效的公鑰
- 某些攻擊可能利用無效點
```

### 2. 小子群攻擊 (Small Subgroup Attack)

如果不驗證點的階(order),攻擊者可能:
- 選擇一個小階的點
- 洩漏私鑰資訊

**防禦:**
- 使用標準曲線(如 P-256)
- 驗證點在正確的子群中

### 3. 曲線混淆攻擊

攻擊者可能:
- 提供其他曲線的點
- 聲稱是 P-256 的點

**防禦:**
- 明確指定曲線參數
- 驗證點確實在預期曲線上

## 總結

### 核心概念回顧

1. **X9.62 是什麼:**
   - ANSI 橢圓曲線標準
   - 定義點的編碼格式
   - 業界標準,廣泛使用

2. **兩種格式:**
   - 未壓縮: `0x04 || X || Y` (65 bytes for P-256)
   - 壓縮: `0x02/0x03 || X` (33 bytes for P-256)

3. **在 App Attest 中的角色:**
   - 憑證的 SPKI 欄位包含 X9.62 格式的點資料
   - SPKI 是完整可用的公鑰格式，可直接用於驗證
   - 理解層次關係：SPKI 包含 X9.62，X9.62 是其中一部分

4. **與其他格式的關係:**
   - X9.62 是「點的表示」（只有座標）
   - SPKI 是「公鑰的完整描述」（點 + 演算法 + 曲線參數）
   - ASN.1 DER 是 SPKI 的編碼方式
   - PEM 是 DER 的 Base64 文字版本

### 為什麼這篇文章很重要?

在實作 App Attest 時:
- ✅ 理解 SPKI 和 X9.62 的層次關係（包含關係，不是轉換關係）
- ✅ 知道從憑證提取的 SPKI 已經可以直接使用
- ✅ 明白 X9.62 是 SPKI 內部的點資料格式
- ✅ 避免誤以為需要「轉換」的常見錯誤

### 與系列文章的關聯

**前置知識:**
- 系列二第 6 篇: ECC 原理 → 理解橢圓曲線點的數學意義
- 第 10 篇: X.509 憑證 → 知道公鑰在憑證的位置
- 第 11 篇: ASN.1 編碼 → 理解 SPKI 的結構

**後續應用:**
- 系列四第 16 篇: Apple App Attest 實例解析 → 實際應用 X9.62 格式轉換

---

**系列**: 密碼學資料格式與編碼 (12/15)
**關鍵詞**: X9.62, 橢圓曲線, 公鑰格式, SPKI, P-256, 格式轉換

## 參考資源

- [ANSI X9.62 標準](https://www.google.com/search?q=ANSI+X9.62)
- [RFC 5480: Elliptic Curve Cryptography Subject Public Key Information](https://www.rfc-editor.org/rfc/rfc5480)
- [SEC 1: Elliptic Curve Cryptography](https://www.secg.org/sec1-v2.pdf)
