device-attestation · L1

# 設備證明 Level 1：Apple App Attest（註冊一次、每次蓋章）

Chance Lu

Level 0 的骨架是：**平台在保險箱裡蓋章，伺服器自己驗章**。這站看 iPhone 上 Apple 的版本 —— **App Attest**：讓你的伺服器確認「連進來的，是跑在真 iPhone 上的正版 App」。

> 一句話：**先請 Apple 替一顆章作證（註冊一次），之後每個重要請求都蓋這顆章（每次蓋章）。**

① 註冊：裝好 App 後做一次（要連 Apple）
  Apple 伺服器替這顆章作證
  iPhone產生一顆章
  你的伺服器驗證明、存下章的樣本
  ① 請 Apple 作證
  ② 回印鑑證明
  ③ 轉交
  ② 每次請求：手機自己蓋章（不連 Apple）
  iPhone用章蓋在請求上
  你的伺服器拿樣本比對
  請求＋章

總覽：上面那段裝好 App 後做一次；下面那段每個重要請求都做（點圖可放大）

## 四個名詞就夠

| 名詞 | 白話 |
| --- | --- |
| **章**（金鑰） | 藏在 iPhone 安全晶片（Secure Enclave）裡的私鑰。誰都拿不出來，只能請晶片幫你蓋。App 手上只有它的號碼牌 `keyId`（其實就是公鑰的指紋）。 |
| **attestation**（註冊） | Apple 開一張「印鑑證明」：這顆章在真的 iPhone、正版 App 裡。**裝好 App 後做一次**；之後只有重裝、換機、從備份還原才要重做，一般的 App 更新不用。 |
| **assertion**（蓋章） | 之後每個重要請求，都用這顆章蓋在「請求內容＋challenge」上。 |
| **counter**（計數器） | 這顆章總共蓋過幾次。每蓋一次 +1。 |

（challenge 是 L0 學過的：伺服器發的一次性亂數，用來防止舊資料被重送。
指紋（雜湊、SHA256）＝把資料壓成一小段代碼，內容改一個字，指紋就完全不同。）

## 第一段：註冊（裝好 App 後做一次）

像去戶政事務所辦印鑑證明：Apple 作證，你的伺服器看過證明後，把**章的樣本（公鑰）**存檔。

Apple 伺服器替金鑰作證
  iPhoneApp＋Secure Enclave
  你的伺服器驗證、存檔
  
  ① generateKey私鑰進 Secure Enclave，拿到 keyId
  ② 我要一個 challenge
  ③ challenge（伺服器記下來）
  ④ 請 Apple 作證：keyId＋challenge 指紋
  ⑤ 印鑑證明（attestation object）
  ⑥ 印鑑證明＋keyId
  ⑦ 問五個問題通過才存：公鑰（章的樣本）
  ⑧ 註冊完成

圖 A：註冊流程。只有第 ④ 步會連到 Apple（點圖可放大）

第 ⑦ 步拆開來看：伺服器收到什麼、檢查什麼、最後留下什麼。

iPhone 送來的
  
  印鑑證明（attestation object）
  
  Apple 簽發的憑證
  · 公鑰（章的樣本）
  · 用 challenge 算的指紋
  · Apple 的簽名
  · App ID
  · counter ＝ 0
  · 開發／正式環境標記
  
  號碼牌 keyId＝公鑰的指紋
  公鑰不另外送，就在憑證裡
  
  你的伺服器：五關都過才存

  1
  是 Apple 開的嗎？憑證一路驗到 Apple 根憑證
  
  ✓
  
  2
  是為這次 challenge 開的？跟自己記下的 challenge 比對
  
  ✓
  
  3
  是這顆章嗎？憑證裡公鑰的指紋 ＝ keyId
  
  ✓
  
  4
  是我的 App 嗎？App ID 是我的
  
  ✓
  
  5
  全新、環境對嗎？counter＝0、環境標記對
  
  ✓
  任一關不過 → 拒絕，什麼都不存
  
  留下來的

  存起來
  公鑰 ↔ 這位使用者
  之後每次蓋章都拿它比對
  用完就丟的
  ✕ challenge（一次性）
  ✕ 印鑑證明（只用這一次）

圖 B：伺服器怎麼處理印鑑證明。五關依序亮起；全過才把憑證裡的公鑰存起來（點圖可放大）

實作時照 Apple 官方的 [11 步驗證清單](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server)逐條寫；每一步都屬於上面五題之一。

## 第二段：每次請求（蓋章）

註冊完之後就**不用再找 Apple**，手機自己蓋章。

iPhone（本機完成，不連 Apple）
  clientData請求內容＋challenge
  SHA256算出指紋
  Secure Enclave用私鑰蓋章counter 7 → 8
  
  請求改一個字 → 指紋不同 → 章對不上
  
  你的伺服器
  驗證 assertion用存檔的公鑰驗章 ✓challenge 對得上 ✓counter 8 &gt; 上次的 7 ✓通過 → 把 8 存起來
  送出
  攻擊者重送舊的 assertion裡面的 counter 還是 7
  7 沒有大於 8→ 拒絕

圖 C：每次請求。請求內容被綁進章裡；counter 每蓋一次就 +1（點圖可放大）

伺服器收到後確認四件事，全過才相信這個請求：

| 確認 | 代表什麼 |
| --- | --- |
| 用存檔的公鑰驗章，通過 | 是那顆註冊過的章，而且請求內容沒被改過 |
| challenge 是我剛發的 | 這張單子是現在寫的 |
| App ID 是我的 | 是我的 App 蓋的 |
| `counter` 比上次大 | 不是舊單子拿來重送（通過後記下新的 counter） |

### counter 為什麼擋得住重送？

counter 本身被蓋在章裡，改了章就對不上；而且它只會變大。伺服器記得上次是 7，攻擊者錄下來重送的舊單子還是 7 → 拒絕。

challenge 已經能擋掉大部分重送；counter 是第二道保險，還多一個好處：如果伺服器看到 counter **停住或倒退**，可能有一個被改過的 App 複本在冒用 —— 這是可疑訊號。

## 常見的坑

- **先問支不支援**：用 `isSupported`。不支援時 App 照常走，伺服器也別硬要蓋章（否則這位使用者永遠過不了），改用其他風控。
- **重裝、換機、從備份還原 → 章失效**，要重新註冊。這些是正常情況，伺服器別一看到新章就拒絕。
- **`keyId` 弄丟就救不回**，產生後馬上存好（例如存在 Keychain：iPhone 上專門放密碼、金鑰的安全儲存區）。
- **開發環境和正式環境的章不互通**，兩邊分開存。

## ✔ 過關標準

能不看筆記回答下面 4 題，這站就過了。

1. 註冊和蓋章各做什麼？哪個要連 Apple？
註冊（attestation）：裝好 App 後做一次（重裝、換機、還原才重做），請 Apple 替新的章作證，要連 Apple。蓋章（assertion）：之後每個重要請求都做，手機本機完成，不連 Apple。
2. 註冊時伺服器要問哪五個問題？
是 Apple 開的嗎？是為這次的 challenge 開的嗎？講的是這顆章嗎？是我的 App 嗎？是全新、環境對的章嗎？
3. counter 為什麼能擋重送？
counter 被蓋在章裡改不了，而且只會變大。伺服器記得上次的值，舊單子的 counter 不會比它大，所以重送會被拒。
4. 什麼情況要重新註冊？伺服器該怎麼看待新章？
重裝、換機、從備份還原，或 keyId 弄丟。這些都是正常情況，伺服器不該一看到新章就拒絕。

## 下一站

Level 2 換到 Android，看 Google 怎麼解同一個問題、跟 Apple 最大的不同在哪。

## 參考來源

- [Apple Developer — Establishing your app's integrity](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)（App 端流程、keyId、重裝後重來）
- [Apple Developer — Validating apps that connect to your server](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server)（伺服器驗證清單）
- [Apple Developer — Preparing to use the App Attest service](https://developer.apple.com/documentation/devicecheck/preparing-to-use-the-app-attest-service)（開發／正式環境）
- [WWDC21 — Mitigate fraud with App Attest and DeviceCheck](https://developer.apple.com/videos/play/wwdc2021/10244/)（counter 防重送）
- [WWDC26 — Secure your apps with App Attest](https://developer.apple.com/videos/play/wwdc2026/201/)（本機蓋章、counter 倒退是可疑訊號、別直接拒絕新金鑰）
