SafetyNet Attestation API

SafetyNet Attestation API 是一种反滥用 API,可以让应用开发者评估运行其应用的 Android 设备。该 API 应当用作滥用检测系统的一部分,以帮助确定您的服务器是否正在与在真实 Android 设备上运行的真实应用互动。

SafetyNet Attestation API 提供采用加密签名的证明,用于评估设备的完整性。为了创建证明,该 API 会检查设备的软件和硬件环境,以查找是否存在完整性问题,并将相应数据与已获批 Android 设备的参考数据进行比较。生成的证明会绑定到调用方应用提供的 Nonce。该证明还包含生成时间戳以及发起请求的应用的元数据。

该 API 用于实现以下用例:

  • 充当独立的反滥用或应用安全机制。请将该 API 与我们发布的应用安全性最佳实践以及您的一套产品专用反滥用信号结合使用。
  • 在设备未连接到互联网时运行。在此类情况下,该 API 会返回错误。
  • 直接在发起调用的应用中解读其响应。将所有反滥用决策逻辑移至您掌控的服务器中。
  • 提供有关系统修改的精细信号。该 API 提供各种布尔值,用于表示不同程度的系统完整性。
  • 纳入应用专属用例的信号,例如设备标识符、GPS 模拟状态和屏幕锁定状态。
  • 替换或实现强健的 DRM 检查。
  • 仅用于检查设备是否已启用 root 权限,因为该 API 旨在检查设备的总体完整性。

概览

SafetyNet Attestation API 使用以下工作流程:

  1. SafetyNet Attestation API 收到您的应用发起的调用。该调用包含一个 Nonce。
  2. SafetyNet Attestation 服务评估运行时环境,并向 Google 的服务器请求已签名的评估结果证明。
  3. Google 的服务器将已签名的证明发送给设备上的 SafetyNet Attestation 服务。
  4. SafetyNet Attestation 服务将此已签名的证明返回给您的应用。
  5. 您的应用将已签名的此证明转发给您的服务器。
  6. 服务器会验证响应,并将其用于反滥用决策。您的服务器会向您的应用传达其发现结果。

此流程的图形描述如图 1 所示:

图 1. SafetyNet Attestation API 协议

注意:其他文档和核对清单

在 SafetyNet Attestation API 的整个初始化、配置和激活过程中,除这份主文档之外,另请了解并遵循以下建议:

获取 API 密钥

如要调用 SafetyNet Attestation API 的方法,您必须使用 API 密钥。SafetyNet Attestation API 已废弃,因此您无法再请求新的 API 密钥。

API 配额和监控

调用 SafetyNet Attestation API 的默认配额(每个项目)为每天 10,000 次请求,这适用于您的所有用户群。如需发出更多完整性请求,请迁移到 Play Integrity API,并根据 Play Integrity API 文档中的说明请求增加配额。请注意,配额请求的处理需要几个工作日的时间,因此建议设置配额监控和提醒,以免发生紧急情况。

注意:无论您为项目配置的配额是多少,每个应用实例每分钟最多可以发出 5 次请求。如果超过此限额,在那一分钟内发出的后续请求都会返回错误。

实现应用的重试机制时,请谨记这一行为。

检查 Google Play 服务版本

您必须先确保用户设备上安装了正确的 Google Play 服务版本,然后再使用 SafetyNet Attestation API。如果安装的版本不正确,您的应用可能会在调用该 API 后停止响应。如果您的应用检测到安装的版本不正确,您应当要求用户更新其设备上的 Google Play 服务应用

如要检查安装的 Google Play 服务版本与您使用的 Android SDK 版本是否兼容,请调用 isGooglePlayServicesAvailable() 方法,如以下代码段所示:

if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
        == ConnectionResult.SUCCESS) {
  // The SafetyNet Attestation API is available.
} else {
  // Prompt user to update Google Play services.
}

在运行 Google Play 服务 v13.0 及更高版本的设备上,SafetyNet Attestation API 还支持应用受到限制的 API 密钥。此功能可降低意外或未经授权使用配额受限的 API 密钥的风险。如需使用此可选功能,请确认设备上 Google Play 服务的最低版本至少为 v13.0,如以下代码段所示:

if (GoogleApiAvailability.getInstance()
    .isGooglePlayServicesAvailable(context, 13000000) ==
    ConnectionResult.SUCCESS) {
  // The SafetyNet Attestation API is available.
} else {
  // Prompt user to update Google Play Services.
}

请求 SafetyNet 证明

当您在 Google API 控制台获取对 Android Device Verification API 有效的 API 密钥后,您的应用就可以使用 SafetyNet Attestation 服务了。为此,请完成以下步骤:

  1. 获取一个 Nonce。
  2. 请求 SafetyNet 证明。
  3. 将响应传输到您的服务器。
  4. 使用服务器上的响应以及其他反滥用信号控制应用的行为。

为使您的应用能迅速响应,请在应用的主执行线程之外执行这些步骤。如要详细了解如何创建单独的执行线程,请参阅将指令发送到多个线程

您应当执行这项检查,为在您的应用中执行的所有关键操作(包括登录、购买事件和获取新应用内商品)提供保护。调用 SafetyNet Attestation API 会导致延迟、移动数据用量和电池用量增加,因此最好在保证安全性和易用性之间找到平衡。例如,您可以选择在登录时请求 SafetyNet 证明,然后最多每 30 分钟重新检查一次。您还可以让服务器决定应用何时请求证明,使攻击者更加难以预测您的检查时间。

获取 Nonce

调用 SafetyNet Attestation API 时,您必须传入一个Nonce。生成的证明会包含此 Nonce,这样您就能确定证明属于您的 API 调用,而不是攻击者重放的。

用于 SafetyNet 请求的 Nonce 的长度应至少为 16 个字节。您的 Nonce 应当具有可变性,从而确保绝不重复使用同一 Nonce。最佳实践是,Nonce 部分派生自发送到服务器的数据。例如,用户名的哈希值与请求时间戳组合到一起形成 Nonce。

重要提示:在 Nonce 中添加尽可能多的数据。这样一来,攻击者就更加难以实施重放攻击了。例如,从用户名派生 Nonce 可限制攻击者对同一账号实施重放攻击。不过,从购买事件的所有详细信息中派生 Nonce 会将 API 的响应消息限制为仅用于该购买事件。

收到 API 发来的带签名的响应后,请务必将带签名的响应中的 Nonce 与根据发送到您服务器的消息的其余部分重建的 Nonce 进行比较。这项检查可确保攻击者无法将从良好设备获取的已签名证明重复用于其他恶意创建的请求。

如需详细了解如何使用加密功能,请参阅有关如何使用加密的指南。

请求证明

与 Google Play 服务建立连接并创建 Nonce 后,您就可以发出 SafetyNet 证明请求了。您的请求可能不会立即得到响应,因此最好设置回调监听器,以处理该服务发出的响应。以下代码段中显示了示例监听器:

Kotlin

SafetyNet.getClient(this).attest(nonce, API_KEY)
    .addOnSuccessListener(this) {
        // Indicates communication with the service was successful.
        // Use response.getJwsResult() to get the result data.
    }
    .addOnFailureListener(this) { e ->
        // An error occurred while communicating with the service.
        if (e is ApiException) {
            // An error with the Google Play services API contains some
            // additional details.
            val apiException = e as ApiException

            // You can retrieve the status code using the
            // apiException.statusCode property.
        } else {
            // A different, unknown type of error occurred.
            Log.d(FragmentActivity.TAG, "Error: " + e.message)
        }
    }

Java

// The nonce should be at least 16 bytes in length.
// You must generate the value of API_KEY in the Google APIs dashboard.
SafetyNet.getClient(this).attest(nonce, API_KEY)
    .addOnSuccessListener(this,
        new OnSuccessListener<SafetyNetApi.AttestationResponse>() {
            @Override
            public void onSuccess(SafetyNetApi.AttestationResponse response) {
                // Indicates communication with the service was successful.
                // Use response.getJwsResult() to get the result data.
            }
        })
    .addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            // An error occurred while communicating with the service.
            if (e instanceof ApiException) {
                // An error with the Google Play services API contains some
                // additional details.
                ApiException apiException = (ApiException) e;
                // You can retrieve the status code using the
                // apiException.getStatusCode() method.
            } else {
                // A different, unknown type of error occurred.
                Log.d(TAG, "Error: " + e.getMessage());
            }
        }
    });

onSuccess() 方法表示与服务之间的通信已成功建立,但并不表示设备已通过 SafetyNet 证明。下一部分介绍了如何解读证明结果及验证其完整性。

将 SafetyNet 证明响应传输到您的服务器

当您的应用与 SafetyNet 进行通信时,该服务会提供包含 SafetyNet 证明结果的响应,同时提供其他有助于您验证消息完整性的信息。证明结果作为 SafetyNetApi.AttestationResponse 对象提供。使用此对象的 getJwsResult() 方法可获取请求的数据。响应格式为 JSON Web Signature (JWS)

将 JWS 对象发送回您的服务器,以供验证和使用。

使用服务器上的 SafetyNet 证明响应

以下 JWS 摘录展示了负载数据的格式和示例内容:

{
  "timestampMs": 9860437986543,
  "nonce": "R2Rra24fVm5xa2Mg",
  "apkPackageName": "com.package.name.of.requesting.app",
  "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                  certificate used to sign requesting app"],
  "ctsProfileMatch": true,
  "basicIntegrity": true,
  "evaluationType": "BASIC",
  "deprecationInformation": "..."
}

已签名的证明的负载通常包含以下字段:

响应的时间戳

  • timestampMs:Google 的服务器生成 JWS 响应消息的时间,以自 UNIX 纪元以来的毫秒数表示。

发起调用的应用提供的数据

  • nonce:发起调用的应用传递到 API 的一次性令牌。

发起调用的应用的相关数据

  • apkPackageName:发起调用的应用的软件包名称。
  • apkCertificateDigestSha256:发起调用的应用签名证书的 SHA-256 哈希 base-64 编码表示法

完整性判定

  • ctsProfileMatch:以比较严格的标准判定设备的完整性。如果 ctsProfileMatch 的值为 true,则表示运行您应用的设备配置文件与已通过 Android 兼容性测试并获批为 Google 认证的 Android 设备的设备配置文件相符。
  • basicIntegrity:以比较宽松的标准判定设备的完整性。如果只有 basicIntegrity 的值为 true,则表示运行您应用的设备可能没有被篡改。不过,该设备不一定已通过 Android 兼容性测试。

    如需详细了解 Android 兼容性测试,请参阅设计 Android 设备、Android 兼容性和兼容性测试套件 (CTS)

废弃信息

  • deprecationInformation:是一个字符串,其中包含了面向开发者的有关废弃 SafetyNet Attestation API 的信息。

可选字段

  • error:与当前 API 请求相关的编码错误信息。
  • advice:有关如何使设备恢复良好状态的建议。
  • evaluationType:促成当前 API 响应的测量类型。

潜在完整性判定

JWS 消息包含以下 2 个参数,用于表示设备兼容性的检查结果:ctsProfileMatchbasicIntegrity。运行您应用的设备的状态会对每个参数值造成影响,如表 1 所示:

表 1. 设备状态如何影响 basicIntegrityctsProfileMatch 的值的示例

设备状态 ctsProfileMatch 的值 basicIntegrity 的值
设备真实且经过认证,并通过了 CTS 测试 true true
设备经过认证,且引导加载程序已解锁 false true
设备真实但未经过认证,例如制造商未申请认证时 false true
设备具有自定义 ROM(未启用 root 权限) false true
模拟器 false false
没有设备(例如协议模拟脚本) false false
设备出现系统完整性受损的迹象,其中一种迹象表明可能存在 root 权限问题 false false
设备出现其他主动攻击的迹象,例如 API 挂接 false false

错误情形

JWS 消息还会显示以下几种类型的错误情况:

  • null 结果表示未成功完成服务调用。
  • JWS 中的 error 参数表示发生了错误,例如网络错误或攻击者伪造的错误。大多数错误是暂时的,当您对该服务发起另一个调用后,这些错误应当就不会显示了。您可能需要重试几次,每次重试之间的延迟会不断增加。
  • 如果设备遭到了篡改,也就是说,如果响应中的 basicIntegrity 设置为 false,则判定可能不包含发起调用的应用的相关数据,例如 apkPackageNameapkCertificateDigestSha256。如果我们的系统无法可靠地确定发起调用的应用,就会出现这种情况。

如果已签名的证明报告错误,该怎么做?

  • 重试。合法设备上的错误是暂时的,当您对该服务发起另一个调用后,这些错误应当就不会显示了。
  • 确认您的应用在受影响的设备上每秒调用 API 的次数不超过 5 次,并确认您项目的 API 配额尚未用尽。
  • 假设可能是攻击者有意触发错误情形,以伪装其活动。

针对通过未来检查给出的建议

如果有,advice 参数会提供相关信息,帮助解释 SafetyNet Attestation API 为何在特定结果中将 ctsProfileMatchbasicIntegrity 设置为 false。该参数的值包含字符串列表,如下例中所示:

{"advice": "LOCK_BOOTLOADER,RESTORE_TO_FACTORY_ROM"}

在您的应用中,您可以将 advice 参数中的值解读为便于用户理解的消息,以帮助用户通过今后的 SafetyNet 证明,如以下列表所示:

LOCK_BOOTLOADER
用户应锁定其设备的引导加载程序。
RESTORE_TO_FACTORY_ROM
用户应将其设备恢复到简洁的出厂 ROM。

评估类型

每当有 ctsProfileMatchbasicIntegrity 判定结果时,evaluationType 参数就会出现。

该参数提供了用于计算特定响应中字段(例如 ctsProfileMatchbasicIntegrity)的测量类型的相关信息。此参数的值包含字符串列表,如以下示例所示:

{"evaluationType": "BASIC,HARDWARE_BACKED"}

目前,可能的值包括:

BASIC
使用典型测量结果和参考数据。
HARDWARE_BACKED
使用由硬件支持的安全功能。其中包括 密钥认证 等功能,适用于搭载 Android 8.0(API 级别 26)及更高版本的设备。

在您的应用中,当 evaluationType 参数中存在 HARDWARE_BACKED 时,则可认为其表示设备的完整性评估更为严格可靠。

注意:仅建议那些已在使用 ctsProfileMatch 判定结果并且需要在设备完整性方面提供最高级别保证的应用依赖 evaluationType 参数,即使这样做可能会限制用户数量。在大多数情况下,您应继续依赖 basicIntegrityctsProfileMatch 判定结果来检测滥用行为。形成这些判定结果时使用了由硬件支持的安全功能(若适用)。

如果您决定依赖 evaluationType 参数中存在的某个值,则应考虑在应用中实现重试机制,以防出现临时错误。

验证 SafetyNet 证明响应

您应当采取措施,以确保 SafetyNet 证明响应确实来自 SafetyNet 服务并包含与您的请求匹配的数据。

如需验证 JWS 消息的来源,请完成以下步骤:

  1. 从 JWS 消息中提取 SSL 证书链。
  2. 验证 SSL 证书链并使用 SSL 主机名匹配来验证叶证书是否已颁发给主机名 attest.android.com
  3. 使用证书来验证 JWS 消息的签名。
  4. 检查 JWS 消息的数据,确保这些数据与原始请求中的数据匹配。具体而言,确保时间戳已经过验证,且应用签名证书的 Nonce、软件包名称和哈希值与预期值相符。

您应当使用标准加密解决方案来验证 JWS 语句,例如 android-play-safetynet 示例 API 用法(可在 GitHub 上获取)中的解决方案。

在初始测试和开发期间(但未进入生产环境),您可以调用在线 API 验证 JWS 语句的签名。此外,android-play-safetynet 示例 API 用法(可在 GitHub 上获取)中也展示了这一过程。请注意,在线验证 API 仅用于早期阶段测试,且每天的固定请求配额为 10,000 次。

重要提示:在线验证 API 仅用于验证 JWS 消息是否已获得 SafetyNet Attestation API 的服务器的签名。此在线 API 无法验证负载中的字段是否与您的服务预期的值相符。

针对意外情况进行规划

我们建议您针对使用情况进行规划,以便将变更和服务中断考虑在内。

API 变更
在判定过程中,可能会随时出现新(实验性)字段。请确保这些额外字段不会中断您的解析器或使用逻辑。具体来说,在 SafetyNet API 客户端邮寄名单中公布实验性字段之前,请勿依赖这些字段。
SafetyNet Attestation API 不可用

在极少数情况下,SafetyNet Attestation API 会无法使用,因此强烈建议使用此 API 的用户开发出服务器端功能,以动态控制对此 API 可用性质量及其响应的依赖。

典型策略应当能够动态指示您的应用停止调用此 API 以及基于设备和基于用户的许可名单,从而针对特定类别的设备和用户忽略 SafetyNet Attestation API 结果。

示例代码

如需获得有关使用 SafetyNet API 的更多指导,请访问 GitHub 查看相关示例代码

公告列表

我们强烈建议加入 SafetyNet API 客户端邮寄名单,以接收有关 SafetyNet Attestation API 的动态更新。

反馈

请考虑提供您对此 API 的反馈。我们会根据您的反馈优先开发此 API 的新特性和功能。

了解详情

如需详细了解使用 SafetyNet Attestation API 时的最佳实践,请参阅以下链接:

附加服务条款

访问或使用 SafetyNet API 即表示您同意 Google API 服务条款及这些附加条款。请先阅读并了解所有适用的条款及政策,然后再使用这些 API。

SafetyNet 服务条款

与通过实际观察大量收集的数据一样,有可能存在误报和漏报情况。我们会尽我们所知呈现数据。我们会对检测机制进行大量测试以确保准确性,而且我们会尽全力逐步改善这些方法,以确保它们继续保持准确性。

您同意遵守所有适用的法律、法规和第三方权利(包括但不限于与数据或软件导入或导出相关的法律、隐私权和当地法律)。您不得使用这些 API 怂恿或宣扬非法活动或侵犯第三方权利。您不得违反 Google(或其关联公司)的任何其他服务条款。

您承认并理解,SafetyNet API 在运行时会收集各种软硬件信息,如设备数据、应用数据和 SafetyNet 证明结果等,并会将这些数据发送给 Google 进行分析。根据 Google API 服务条款第 3(d) 条的规定,您同意使用这些 API 即表示您负责就收集此类数据以及与 Google 共享此类数据提供任何必要的通知或同意。

SafetyNet Attestation 数据安全

Google Play 有一个数据安全部分,供开发者披露其应用的数据收集、分享和安全做法。为了满足“数据安全”部分的要求,您可以参考以下信息,了解 SafetyNet Attestation API 是如何处理数据的:

实践类型 SafetyNet Attestation API 如何应用该实践
收集的使用情况数据
  • 软件包名称
  • 应用签名证书
  • Google Play 服务生成的设备证明令牌
数据收集目的 我们会利用收集的数据来验证应用完整性和设备完整性。
数据加密 数据未加密。
数据分享 我们不会将收集的数据发送给任何第三方。
数据删除 我们会在固定保留期限过后删除收集的数据。

如需完成数据披露,您可以使用 Android 的数据类型指南来确定哪些数据类型能准确描述所收集的数据。在数据披露中,另请务必明确说明您的特定应用将如何共享和使用所收集的数据。

虽然我们力求尽可能透明地为您提供支持,但我们无法代表您,并且对于 Google Play 的“数据安全”部分针对您的应用的用户数据收集、分享和安全做法提供的表单,您需自行负责决定如何回应。