用 App Attest 和 DeviceCheck 防范欺诈
Mitigate fraud with App Attest and DeviceCheck
2021年6月10日
一句话判断
如果你的 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 的速率限制信息,让你知道还剩多少配额。