Reference version

This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 55).

AppIntegrity

一个库,提供对 Android 上 Google 的 Play Integrity API 和 iOS 上 Apple 的 App Attest 服务的访问。

Android
iOS
Included in Expo Go

For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

重要 此库目前处于 alpha 阶段,并且会频繁出现破坏性变更。

@expo/app-integrity 提供了一组 API,帮助确保你的后端资源仅被运行在真实设备上的合法应用安装访问。它在 Android 上使用 Google 的 Play Integrity APIs,在 iOS 上使用 Apple 的 App Attest service 来验证应用真实性,帮助防止未授权客户端、被修改的应用或自动化脚本向你的服务器发起请求。

通常,@expo/app-integrity 帮助你的服务器区分以下两者:

  • 运行在 真实设备 上的你的 真实应用
  • 其他任何东西(修改过的应用、脚本、模拟器)

它通过使用平台推荐的应用证明服务来实现这一点。

安装

Terminal
npx expo install @expo/app-integrity

If you are installing this in an existing React Native app, make sure to install expo in your project.

在 Android 上的使用

@expo/app-integrity 使用 Play Integrity 的 标准请求流程 进行完整性检查。

配置

请参考 Play Integrity 设置指南 了解如何在你的应用中启用完整性 API。

准备完整性令牌提供器(一次性)

在发起完整性检查请求之前,你需要先准备完整性令牌提供器。你可以在应用启动时执行,或在需要完整性检查之前于后台完成。

import * as AppIntegrity from '@expo/app-integrity'; const cloudProjectNumber = 'your-cloud-project-number'; await AppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);

请求完整性令牌(按需)

每当你的应用发起一个你希望验证其真实性的服务器请求时,你都需要请求一个完整性令牌,并将其发送到你应用的后端服务器进行解密和验证。然后,后端服务器就可以决定如何处理。

const requestHash = '2cp24z...'; const result = await AppIntegrity.requestIntegrityCheckAsync(requestHash);

在调用 requestIntegrityCheckAsync 之前,请确保已经成功调用过 prepareIntegrityTokenProviderAsync

在这个示例中,requestHash 是与被验证的特定用户操作唯一对应的哈希值。你可以针对不同的用户操作,使用不同的哈希值多次调用 requestIntegrityCheckAsync

成功后,将结果发送到你的服务器进行验证。

注意:如果你的应用长时间使用同一个令牌提供器,令牌提供器可能会过期,从而在下一次请求令牌时导致 ERR_APP_INTEGRITY_PROVIDER_INVALID 错误。你应通过再次调用 prepareIntegrityTokenProviderAsync 来请求新的提供器,从而处理此错误。

解密并验证完整性判定结果

请参考 Play Integrity 指南 以在你的服务器中验证完整性令牌。

其他资源

  • Google Pay Integrity 文档:参考 Google 的官方指南,了解支撑 @expo/app-integrity 的 API 和验证流程。

  • Play Integrity 标准请求流程:此页面描述了如何发起标准 API 请求以获取完整性判定结果,这些请求支持 Android 5.0(API 级别 21)及更高版本。每当你的应用在发起服务器调用以检查交互是否真实时,都可以发起一次标准 API 请求来获取完整性判定结果。

  • 关于完整性判定结果:完整性判定结果会传达有关设备、应用和账户有效性的信息。你的应用服务器可以使用解密并验证后的判定载荷,来决定针对应用中的某个特定操作或请求应如何处理。

  • 处理错误代码:如果你的应用发起 Play Integrity API 请求且调用失败,应用会收到一个错误代码。这些错误可能由多种原因引起,例如网络连接较弱等环境问题、API 集成问题,或恶意活动和正在进行的攻击。

在 iOS 上的使用

配置

在 Xcode 中,进入 Signing & Capabilities,点击 + Capability,添加 App Attest。Xcode 会自动为你的应用添加所需的 entitlement。

注意:要使用 App Attest 服务,你的应用必须具有你在 Apple Developer 网站上注册的 App ID。

有关服务器上的验证逻辑,请参见 验证连接到你服务器的应用

检查设备是否支持应用证明

并非所有设备都能使用 App Attest 服务,因此在访问该服务之前,让应用先进行兼容性检查非常重要。如果用户的应用未通过兼容性检查,请优雅地绕过该服务。你可以通过读取 isSupported 属性来检查是否可用。

import * as AppIntegrity from '@expo/app-integrity'; if (AppIntegrity.isSupported) { // 执行密钥生成和证明。 } // 继续进行你的服务器 API 访问。

注意:iOS 模拟器不支持 App Attest。

信息 大多数应用扩展都不支持 App Attest。通常,在这些扩展中执行代码时,即使 isSupported 方法属性为 true,也应绕过密钥生成和证明。唯一支持 App Attest 的应用扩展是 watchOS 9 或更高版本中的 watchOS 扩展。对于这些扩展,你可以使用 isSupported 的结果来判断你的 WatchKit 扩展是否绕过证明。

创建密钥对

对于运行你应用的每台设备上的每个用户账户,通过调用 generateKey 方法生成一对唯一的、基于硬件的加密密钥对。

const keyId = await AppIntegrity.generateKeyAsync();

成功后,该方法会返回一个密钥标识符(keyId),你稍后会用它来访问该密钥。请将该标识符记录到持久化存储中,因为没有标识符就无法使用该密钥,而且之后也无法再获取该标识符。设备会自动将关联的私钥存储在 Secure Enclave 中,App Attest 服务可以从中使用它来创建签名,但任何进程都无法直接读取或修改它,从而确保其安全性。

信息 如果你在 App Clip 中创建了一个密钥对,请在对应的完整应用中使用同一密钥对。为此,请确保将该标识符存储在完整应用可访问的共享容器中。有关在应用/扩展之间共享数据库,请参见 Expo 关于使用 expo-sqlite 的指南,或使用 React Native MMKV 的 App Groups / extensions 共享存储,以便在两个目标之间持久保存该标识符。

不要在同一设备上的多个用户之间复用同一个密钥,因为这会削弱安全保护。特别是,这会使检测利用一台被攻破的设备为多个运行被篡改版本应用的远程用户提供服务的攻击变得更加困难。更多信息请参见 评估欺诈风险

从你的服务器获取挑战值

从你的服务器请求一个唯一的一次性挑战值。该挑战值将嵌入到下面的证明步骤中,确保攻击者无法重用它。挑战值至少应为 16 字节长,以提供足够的熵,使其难以被猜测。

将密钥对认证为有效

如下所示,在 attestKey 方法中,将 keyId 与前面步骤中从你的服务器创建的挑战值一起传入:

const attestationObject = await AppIntegrity.attestKeyAsync(keyId, challenge);

成功后,将收到的 attestationObjectkeyId 一并发送到你的服务器进行验证。

如果该方法返回 ERR_APP_INTEGRITY_SERVER_UNAVAILABLE 错误,请稍后使用相同的密钥再次尝试证明。对于任何其他错误,请丢弃该密钥标识符,并在下次重试时创建一个新密钥。

信息 如果你的应用已经拥有数百万日活用户,并且你想开始从应用中调用 attestKey 方法来发起证明,请查看 准备使用 app attest 服务 以获得关于如何安全地逐步放量给用户的指导。

如果服务器能够成功验证证明对象,则会认为该应用实例是有效的。在这种情况下,请务必在应用中持久化存储密钥标识符 — 而不是证明对象 — 以便将来对服务器请求进行签名。

对敏感请求生成断言

在成功验证某个密钥的证明之后,你的服务器可以要求应用对未来的全部或部分服务器请求进行真实性断言。应用通过对请求进行签名来实现这一点。在应用中,从服务器获取一个唯一的一次性挑战值。这里使用挑战值的目的与证明时相同,都是为了避免重放攻击。

const challenge = 'A string from your server'; const request = { action: 'getGameLevel', levelId: '1234', challenge: challenge, }; const assertion = await AppIntegrity.generateAssertionAsync(keyId, JSON.stringify(request));

成功后,将断言对象连同客户端数据一起传递给服务器。如果断言对象验证失败,由你来决定如何处理该请求。

你可以使用同一个密钥生成的断言数量不受限制。尽管如此,通常你会将断言保留给应用生命周期中敏感时刻发出的请求,例如应用下载高级内容时。

在重新安装后重新开始

你生成的密钥会在常规应用更新后继续有效,但在应用重新安装、设备迁移或从备份恢复设备后不会保留。在这些情况下,你需要从头开始并生成新密钥。请尽量将新密钥的生成限制在这些事件发生时,或者新增用户时。保持设备上的密钥数量较少,有助于检测某些类型的欺诈行为。

其他资源

API

import * as AppIntegrity from '@expo/app-integrity';

Constants

AppIntegrity.isSupported

iOS

Type: boolean

A boolean value that indicates whether a particular device provides the App Attest service. Not all device types support the App Attest service, so check for support before using the service.

Methods

AppIntegrity.attestKeyAsync(keyId, challenge)

iOS
ParameterTypeDescription
keyIdstring

The identifier you received by calling the generateKey function.

challengestring

A challenge string from your server.


Asks Apple to attest to the validity of a generated cryptographic key.

Returns:
Promise<string>

A Promise that is fulfilled with a string that contains the attestation data. A statement from Apple about the validity of the key associated with keyId. Send this to your server for processing.

AppIntegrity.generateAssertionAsync(keyId, challenge)

iOS
ParameterTypeDescription
keyIdstring

The identifier you received by calling the generateKey function.

challengestring

A string to be signed with the attested private key.


Creates a block of data that demonstrates the legitimacy of an instance of your app running on a device.

Returns:
Promise<string>

A Promise that is fulfilled with a string that contains the assertion object. A data structure that you send to your server for processing.

AppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge)

Android
ParameterTypeDescription
keyAliasstring

A unique identifier for the key.

challengestring

A challenge string from your server.


Generates a hardware-attested key pair in the Android Keystore. This key can be used for attestation on GrapheneOS and other secure Android distributions.

Returns:
Promise<void>

A Promise that resolves when the key is generated successfully.

AppIntegrity.generateKeyAsync()

iOS

Creates a new cryptographic key for use with the App Attest service.

Returns:
Promise<string>

A Promise that is fulfilled with a string that contains the key identifier. The key itself is stored securely in the Secure Enclave.

AppIntegrity.getAttestationCertificateChainAsync(keyAlias)

Android
ParameterTypeDescription
keyAliasstring

The identifier of the key to get certificates for.


Retrieves the attestation certificate chain for a hardware-attested key. The certificate chain can be validated on your server to verify device integrity.

Returns:
Promise<string[]>

A Promise that is fulfilled with an array of base64-encoded X.509 certificates.

AppIntegrity.isHardwareAttestationSupportedAsync()

Android

Checks if hardware attestation is supported on this device.

Returns:
Promise<boolean>

A Promise that is fulfilled with a boolean indicating support.

AppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber)

Android
ParameterTypeDescription
cloudProjectNumberstring

The cloud project number.


Prepares the integrity token provider for the given cloud project number.

Returns:
Promise<void>

A Promise that is fulfilled if the integrity token provider is prepared successfully.

AppIntegrity.requestIntegrityCheckAsync(requestHash)

Android
ParameterTypeDescription
requestHashstring

A string representing the request hash.


Requests an integrity verdict for the given request hash from Google Play.

Returns:
Promise<string>

A Promise that is fulfilled with a string that contains the integrity check result.