Mitigate fraud with App Attest and DeviceCheck
Privacy & Security 进阶 20m

用 App Attest 和 DeviceCheck 防范欺诈

Mitigate fraud with App Attest and DeviceCheck

2021年6月10日

在 Apple 官方观看视频

一句话判断

如果你的 App 有积分、内购或任何有价值的数字资产,App Attest 是防止篡改和伪造请求的必备防线——它在设备上生成不可伪造的断言(Assertion),让你的服务器可以验证请求确实来自一个未被篡改的 App。

这场 Session 讲了什么

Session 详细介绍了两个安全框架:App Attest 和 DeviceCheck,以及它们如何配合防范欺诈。

App Attest 在 iOS 14 引入,iOS 15 继续增强。它的工作原理是:在设备上生成一对密钥对,私钥由 Secure Enclave 保护,不可提取;然后让 Apple 服务器签发一个证书证明这个密钥属于一个合法的 App 实例。之后你的 App 用这个私钥对关键请求签名,你的服务器通过验证签名和证书来确认请求的真实性。

DeviceCheck 是较简单的 API,让你给每个设备设置两个 bit 的状态信息,存储在 Apple 服务器上。常用于标记设备是否已经领取过某个优惠。

Session 强调了两个框架的互补关系:DeviceCheck 解决”这是不是同一台设备”的问题,App Attest 解决”这个请求是不是来自真实的 App”的问题。

值得深挖的点

App Attest 的信任链

App Attest 建立了一条从 Secure Enclave 到 Apple 再到你的服务器的信任链。设备上的 App 生成密钥 -> Apple 验证 App 的签名和完整性后签发证书 -> App 用密钥签名请求 -> 你的服务器用 Apple 的根证书验证整条链。攻击者即使完全控制了越狱设备的文件系统,也无法伪造 App Attest 的断言——因为私钥在 Secure Enclave 中,无法导出。

欺诈检测的多层防御

单一防线不够。Session 建议的完整方案是:DeviceCheck 做设备级别的频率限制(同一台设备不能无限领优惠);App Attest 做请求级别的完整性验证(确保请求来自未篡改的 App);服务端做业务逻辑的风控(异常行为检测、IP 限制)。三层配合才能有效防范欺诈。

代码片段

App Attest 完整流程

import DeviceCheck

// 1. 检查设备是否支持 App Attest
guard DCAppAttestService.shared.isSupported else {
    // 降级处理
    return
}

// 2. 生成密钥对
let keyId = try await DCAppAttestService.shared.generateKey()

// 3. 获取 Apple 签发的证书
let clientDataHash = Data(SHA256.hash(data: challenge))
let attestationObject = try await DCAppAttestService.shared.attestKey(
    keyId, clientDataHash: clientDataHash
)

// 4. 发送 attestationObject 到你的服务器验证
try await sendToServer(attestation: attestationObject)

// 5. 使用密钥签名后续请求
let requestHash = Data(SHA256.hash(data: requestData))
let signature = try await DCAppAttestService.shared.generateSignature(
    keyId, clientDataHash: requestHash
)
// 把 signature 和 keyId 发给服务器验证

服务端验证 Attestation

// 服务端(Python 示例,非 Swift)
// 1. 解码 attestation CBOR 数据
// 2. 验证 Apple 的证书链
// 3. 检查 App 的 bundle ID 和 team ID 是否匹配
// 4. 验证 nonce(防止重放攻击)
// 5. 存储公钥和 keyId

// 验证签名:
// def verify_assertion(key_id, signature, data):
//     public_key = stored_keys[key_id]
//     # 用公钥验证签名
//     if verify_ecdsa(public_key, signature, data):
//         return True
//     return False

使用 DeviceCheck 标记设备

import DeviceCheck

// 获取设备 token
let token = try await DCDevice.current.generateToken()

// 发送到服务器,用 Apple 的 API 更新设备的两个 bit
// 服务端调用 Apple DeviceCheck API:
// POST https://api.development.devicecheck.apple.com/v1/update_two_bits
// {
//   "device_token": "...",
//   "transaction_id": "...",
//   "timestamp": 1234567890,
//   "bit0": true,   // 例如:已领取新用户优惠
//   "bit1": false
// }

// 查询设备状态
// POST https://api.development.devicecheck.apple.com/v1/query_two_bits

最佳实践

集成策略: 优先保护高价值操作(领取积分、兑换优惠、修改账户信息)。App Attest 的 generateSignature 有频率限制,不要对每个请求都签名——只对关键操作签名。缓存 keyId,避免重复生成密钥。

降级处理: 不是所有设备都支持 App Attest(旧设备、模拟器)。对于不支持的设备,用 DeviceCheck + 服务端风控作为降级方案。不要因为设备不支持就直接拒绝服务。

还有什么值得关注

  • App Attest 在 Debug 和 Release 环境使用不同的 Apple 服务器端点。
  • DeviceCheck 的两个 bit 是持久的,卸载 App 也不会重置。
  • iOS 15 新增了 App Attest 的速率限制信息,让你知道还剩多少配额。
WWDC 2021