設備證明 Level 3:伺服器端統一驗證兩個平台

Chance Lu · 學習文章 · 2026-10-22 15:32
Chance Lu

前三站講的是「每個平台怎麼運作」:L0 的骨架(平台在保險箱裡蓋章,伺服器端驗章)、L1 的 Apple App Attest、L2 的 Google Play Integrity。這一站是最後一站,講你的伺服器要怎麼設計,才能同時接住兩邊。

這站學完,你要能:
① 畫出一套伺服器驗證架構:發 challenge → 綁請求內容 → 分流驗證 → 正規化 → 依策略回應。
② 說出 Apple 和 Google 各要存什麼、各要呼叫什麼。
③ 說出「附了證明但驗不過」和「根本拿不到證明」各該怎麼處理,以及理由。
④ 說出設備證明在整體防線裡的位置。

怎麼看這站的標記:官方文件寫的內容,會在句尾附上出處。官方沒有要求、是設計上的選擇,會標 設計建議(非官方),你可以依自己的情況調整。

起點:L2 的對照表,伺服器要吸收的四個差異

Apple App AttestGoogle Play Integrity伺服器要處理什麼
誰驗你的伺服器自己驗Google 伺服器解密驗證兩套驗證邏輯
一次性註冊要,伺服器存公鑰不用伺服器端註冊只有 Apple 要一張註冊表
防重送challenge+counterstandard 由 Google 擋大量重複使用兩種防重送手段
拿到什麼證明原料,結論自己算判斷好的判定結果要翻成同一種「共同判定」

先認得這站的新名詞

名詞白話
驗證器(verifier)伺服器裡專門驗某個平台證明的那一塊程式。Apple 一個、Google 一個。
註冊表伺服器存「Apple 金鑰」的地方:keyId 對應到公鑰和最新的 counter。就是 L1 說的印鑑樣本存檔。
正規化把兩個平台長得不一樣的結果,翻譯成同一種格式,後面的程式只看這一種。
共同判定正規化之後的結果,例如「App 可信嗎、裝置可信嗎、這次請求乾淨嗎」。
策略「看到什麼判定,就怎麼回應」的規則表。

角色不變:App 遞件、平台蓋章、你的伺服器驗章(或請 Google 的驗件窗口代驗,就是 L2 說的拿去窗口拆封)並做決定。這站所有新東西都在「你的伺服器」裡。

統一流程的骨架

AppiOS/Android 你的伺服器 ① 發 challenge一次性、有時效 註冊入口只有 Apple ③ 分流看是哪個平台 Apple 驗證器驗章、challenge、counter 註冊表keyId → 公鑰+counter寫入前查公鑰沒綁別人 Google 驗證器requestHash、時間戳 ④ 正規化共同判定 ⑤ 策略決定回應 Google 伺服器解密 token ② 請求+證明(已綁請求內容) 回應:放行/限制/加驗/拒絕

圖 A:統一驗證架構。橘框的註冊入口只有 Apple 用;Google 那一路要呼叫 Google 伺服器。編號對應下面五步(點圖可放大)

  1. 發 challenge:
    • Apple:伺服器要提供隨機、一次性的 challenge,並記住它(Apple:Validating 頁)。WWDC26 也說,註冊(attestation)要由伺服器控制何時發起,理由是讓 App 維持在安全的每秒請求量以內。
    • Google standard 請求不需要伺服器發 challenge(Android:standard 頁)。
    • 兩個平台共用同一個發 challenge 入口,給 challenge 很短的有效時間,並綁定發給哪個使用者或 session 設計建議(非官方)。
  2. 綁定請求內容:本系列把 Apple 的 clientDataHash(assertion 時)和 Google 的 requestHash 對照來看,它們都是「把這次請求的內容做成雜湊,交給平台一起簽進去」,所以可以用同一套程式產生。這個對照是本系列的整理,官方沒有這樣並列。
  3. 按平台分流驗證:看請求來自哪個平台,交給對應的驗證器,檢查項目照 L1、L2 的官方清單。
  4. 正規化成共同判定:兩個驗證器都輸出同一種格式 設計建議(非官方)。
  5. 依策略回應:用分級回應,而不是只有放行和拒絕(Android:Overview 頁)。兩個平台共用同一張策略表 設計建議(非官方)。

註冊入口(只有 Apple):App 送來 attestation object → Apple 驗證器驗過 → 寫入註冊表;寫入前確認這把公鑰沒有綁在別的使用者身上(Apple:Validating 頁)。

Google 那一路要不要也自己發 challenge?

一句話:一個入口發 challenge,兩個驗證器各驗各的,翻成同一種判定,再用同一張策略表回應;只有 Apple 多一條註冊入口。

平台差異怎麼吸收:各存什麼、各呼叫什麼

Apple 驗證器Google 驗證器
要存什麼註冊表:公鑰(綁定使用者和裝置)、最新 counter、receipt;開發和正式環境分開存;一個使用者可能有多組(Apple:Validating 頁)
發出去、還沒用掉的 challenge(Apple:Validating 頁)
service account 的憑證,用來呼叫 Google API(Android:standard 頁)
classic:發過或看過的 nonce 紀錄(Android:classic 頁)
若採用「Google 那一路也發 challenge」:發出去、還沒用掉的 challenge 設計建議(非官方)
要呼叫什麼驗證時不用呼叫 Apple;只有選用的風險指標,才用 receipt 去問 Apple 伺服器(Apple:Assessing fraud risk 頁)每次都呼叫 decodeIntegrityToken 請 Google 解密(Android:standard 頁)
註冊時多做的事確認這把公鑰沒有綁在別的使用者身上(Apple:Validating 頁)無

位元組層級的驗證步驟(OID、X9.62 等),照 L1 收合區塊的逐步對照表做,這裡不重講。

兩道一次性檢查都要原子化

原子化=「讀出舊值、比較、寫入新值」一次做完,中間不讓別的請求插隊。Apple 那一路有兩道一次性檢查:challenge 的消耗和 counter 的更新。

  1. 攻擊者把同一個 assertion 同時送兩次。
  2. 如果兩個請求都先讀到「challenge 還沒用過」、「counter 是 7」,就都會通過,重送成功。
  3. 所以兩道檢查都要一次完成 設計建議(非官方):
    • 第一道防線是 challenge:只有當這個 challenge 還沒被用過,才把它標成已用。
    • counter:只有當註冊表裡存的 counter 小於這次收到的 counter,才寫入。
原子化的做法與副作用(實作時看)
一句話:Apple 那一路存註冊表、自己驗;Google 那一路幾乎不存東西、每次請 Google 解密;challenge 和 counter 兩道一次性檢查都要原子化。

正規化:共同判定長什麼樣

下面是一種共同判定的格式,欄位名稱是本系列自訂的 設計建議(非官方)。

共同判定在問什麼Apple 來源Google 來源
請求乾淨沒被重送、沒被竄改嗎?簽章驗得過、challenge 對得上、counter 有往上加requestDetails 對上、時間戳夠新
App 可信是我那個沒被改過的 App 嗎?註冊時 attestation 通過+這次用註冊過的金鑰簽+RP ID 對上appRecognitionVerdict
取得管道/授權使用者是從官方商店取得的嗎?無對應appLicensingVerdict
裝置等級裝置可信到哪一級?沒有分級(對應方式見下)deviceRecognitionVerdict 的標籤
額外風險訊號有沒有其他可疑跡象?選用的風險指標、counter 停住或倒退recentDeviceActivity、environmentDetails

從判定到回應:決策圖

收到請求可能附有證明 ① 有證明、拿得到結果?支援、服務正常、配額夠 沒證明/拿不到結果當成「不可信」那一級 ② 驗得過嗎?附了證明就一定要驗過 驗不過 → 拒絕 ③ 可信到哪一級?App 可信+裝置等級 高 → 放行 中 → 限制功能 低 → 加驗或請使用者補救 不可信 → 依敏感度限制或加驗 否 是 否 是

圖 B:從共同判定到分級回應。整張決策圖是設計建議(非官方);分級回應的想法來自 Google 官方建議(點圖可放大)

一句話:附了證明但驗不過就拒絕;拿不到證明就當「不可信」那一級;驗過了再看可信到哪一級。

拿不到證明時:各種情況怎麼處理

情況官方怎麼說設計上怎麼接
裝置不支援Apple:isSupported 是 false 時優雅略過,伺服器那邊 "can no longer require assertions"(Apple:Establishing 頁)
Android:兩種請求都需要 Google Play Store 與 Google Play services(Android:classic 頁)
「不支援」是 App 自己回報的,伺服器無法驗證,所以攻擊者可以假裝不支援;因此高風險操作不能因為「不支援」就放行 設計建議(非官方)。另一面,L0 引過的 OWASP Platform Lock-in 提醒:太依賴平台服務,會把用其他 Android 版本的正常使用者擋在外面,所以低風險操作要留一條路。
平台服務故障、逾時Apple:serverUnavailable 是 attestation 時連不到 Apple(Apple:DCError 頁),而 assertion 在裝置本機產生(WWDC26),所以只影響新註冊,已註冊的裝置照常能驗
Google:每次請求都要靠 Google 伺服器解密;官方建議事先規劃大規模故障時後端怎麼運作(Android:Overview 頁)
替伺服器呼叫 decodeIntegrityToken 設逾時上限,逾時就當成「拿不到結果」 設計建議(非官方)
配額用完、被限流Google:預設每天 10,000 次,可申請提高,建議設用量警報(Android:Setup 頁);官方也建議在盡量接近要保護的操作時才要求判定(Android:Overview 頁,原文講的是 app access risk 判定)
Apple:被限流時要能把請求量降下來(Apple:Preparing 頁);assertion 通常只留給敏感時刻(Apple:Establishing 頁)
照官方方式處理:退避、提前申請提高配額、只在適當時機才要求證明
重試後仍失敗Google:背景操作(App 端的指引)用指數退避,重試三次仍失敗就當作沒通過所有檢查(Android:Error codes 頁);使用者主動操作時,官方建議給看得懂的錯誤訊息,請使用者重試、開網路或更新 Play Store(Android:Overview 頁)當成「拿不到結果」,交給策略表
重裝 App、換新機Apple:金鑰在重裝、換機、從備份還原後失效,要重新註冊;不要一看到新金鑰就拒絕(Apple:Establishing 頁、WWDC26)
Google:官方 standard 流程裡伺服器沒有存裝置資料,所以 App 重新準備即可
允許重新註冊。Apple 官方建議只在這些情況才產生新金鑰,因為 "Keeping the key count low on a device helps when trying to detect certain kinds of fraud"(Apple:Establishing 頁)。官方是以裝置為單位;延伸成以帳號為單位,記錄同一帳號短時間內換了幾把金鑰、異常時加驗 設計建議(非官方)

拿不到證明時:fail-open 還是 fail-closed

一句話:拿不到證明時,當成「不可信」那一級;低風險放行但限制,高風險加驗或擋下。

上線策略

先只收集、不擋人:Google 官方建議先接上 API、不執行,了解現有使用者會拿到什麼判定,再估算影響(Android:Overview 頁)。Apple 那一路也照樣做,觀察一段時間後再從最敏感的操作開始逐步收緊 設計建議(非官方)。

分批上線與配額(實作時看)
建議監控的指標(實作時看)
一句話:先觀察再收緊;Apple 真正的上限是每秒 100 次,Google 的配額要在上線前申請好。

設備證明在整體防線裡的位置

一句話:設備證明讓攻擊變貴,但登入、權限和業務規則還是得在伺服器端做好。

全系列回顧

✔ 過關標準

能不看筆記回答下面 5 題,這條 roadmap 就走完了。

  1. 畫出統一驗證的五個步驟,以及 Apple 多出來的那條路。看答案收合
    發 challenge → 綁定請求內容(clientDataHash/requestHash)→ 按平台分流給 Apple 或 Google 驗證器 → 正規化成共同判定 → 依策略分級回應。Apple 另有註冊入口:驗 attestation、寫入註冊表,並確認公鑰沒綁在別的使用者上。
  2. Apple 和 Google 驗證器各要存什麼、各要呼叫什麼?看答案收合
    Apple:存註冊表(公鑰、counter、receipt)和發出去的 challenge;驗證時不用呼叫 Apple。Google standard:存 service account 憑證,不必存每台裝置的資料(若自己發 challenge 就要存 challenge),每次呼叫 decodeIntegrityToken 請 Google 解密。
  3. 為什麼 challenge 和 counter 都要原子化?看答案收合
    同一個 assertion 同時送兩次時,兩個請求可能都讀到「challenge 沒用過、counter 是舊值」,都通過,重送就成功了。第一道防線是 challenge 的消耗,第二道是 counter 的更新,兩道都要一次做完。(這是設計建議,官方只要求 challenge 一次性、counter 大於上次。)
  4. 「附了證明但驗不過」和「拿不到證明」各怎麼處理?fail-open 和 fail-closed 是什麼?看答案收合
    驗不過:直接拒絕。拿不到證明:當成「不可信」那一級,交給策略表。fail-open=拿不到證明時先放行但限制,適合低風險操作;fail-closed=拿不到證明時先加驗或擋下,適合高風險操作。
  5. 設備證明能取代登入驗證嗎?為什麼?看答案收合
    不能。它只回答「是不是正版 App、真裝置」。Google、Apple、OWASP 都說它只是整體防線的一部分;登入、權限和業務規則仍要在伺服器端做。

延伸閱讀

參考來源