
从 Changelog 读懂 expo-app-integrityExpo 应用完整性校验模块的版本演进与实现剖析【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expoexpo-app-integrity是 Expo 生态中用于在移动端断言应用本身是否被篡改、是否运行在可信环境的原生模块对应仓库目录为 packages/expo-app-integritynpm 包名expo/app-integrity。它在 iOS 上封装 Apple 的 App Attest / DeviceCheck 服务在 Android 上封装 Google Play Integrity API并提供面向 GrapheneOS 等安全发行版的硬件认证Hardware Attestation能力。本文将以其 CHANGELOG.md 的版本记录为主线结合 AppIntegrity.ts 的 JS API、IntegrityModule.swift 与 IntegrityModule.kt 的原生实现完整还原该模块自 0.1.7 起每一次用户可见或破坏性变更的技术实质。读完本文你将能读懂该 changelog 中每条记录对应的代码影响掌握 iOS/Android 两侧 API 的演进结果与错误码体系并能据此判断某个版本对你的项目是否构成升级风险。模块定位一份 Changelog 背后横跨三个完整性方案的 API 面要理解这份 changelog 的演变先要认清它记录的对象——这个模块在公开 API 层一共暴露了三个彼此独立的完整性校验能力域从 AppIntegrity.ts 与 ExpoAppIntegrity.types.ts 中可以清晰看到全貌iOS —— App AttestisSupported常量 generateKeyAsync()/attestKeyAsync(keyId, challenge)/generateAssertionAsync(keyId, challenge)。密钥生成在设备 Secure Enclave 中完成应用仅持有 keyId私钥永不出设备。Android —— Play Integrity标准模式prepareIntegrityTokenProviderAsync(cloudProjectNumber)requestIntegrityCheckAsync(requestHash)走 Google PlayStandardIntegrityManager用于上架 Play 商店的常规应用。Android —— 硬件认证Hardware AttestationisHardwareAttestationSupportedAsync()/generateHardwareAttestedKeyAsync(keyAlias, challenge)/getAttestationCertificateChainAsync(keyAlias)走 Android Keystore 的KeyGenParameterSpec.setAttestationChallenge可服务于不依赖 Play 服务的场景。两个平台在 JS 层的函数签名几乎一一对应原生侧。原生模块在 iOS 与 Android 都注册为ExpoAppIntegrity见 IntegrityModule.swift 与 IntegrityModule.kt 中的Name(ExpoAppIntegrity)JS 层通过 ExpoAppIntegrity.ts 中的requireNativeModuleExpoAppIntegrityModule(ExpoAppIntegrity)拿到句柄。值得注意的是公开头文件 index.ts 只导出了AppIntegrity同时仓库中 ExpoAppIntegrity.web.ts 提供的是空对象实现即该能力是纯原生能力Web 平台不提供实现。这份 changelog 里0.1.x 阶段的数次实质性变更 55/56/57 大版本下的稳定性维护这条曲线恰好对应了该模块从快速迭代期走向成熟期的过程。版本演进时间线Changelog 记录逐条还原以下把 CHANGELOG.md 中的全部版本记录完整继承并整理成时间线。其中 0.1.x 段的变更直接决定了今天 API 的形态而 55.x–57.x 段则反映了它并入 SDK 常规版本节奏后的状态0.1.x → 55.x 的跳号与 Expo 模块统一跟随 SDK 大版本号对齐有关可在仓库根目录 CHANGELOG.md 与各模块版本策略中印证。版本日期变更类型要点0.1.72025-09-11 Bug fixesiOS 修复attestKey/generateAssertion因二进制数据 UTF-8 解码不当而失败的问题改为按要求返回 base64 编码字符串0.1.82025-09-22 New featuresAndroid 新增硬件认证hardware attestation支持0.1.92025-10-20 Breaking changes函数统一重命名增加async后缀0.1.102025-12-05—无用户可见变更55.0.0–55.0.132026-01-21 ~ 2026-04-09—连续多个版本均无用户可见变更稳定性维护、依赖同步56.0.02026-05-05 Breaking changes最低支持版本上调iOS/tvOS 至 16.4、macOS 至 13.456.0.1–56.0.32026-05-06—无用户可见变更57.0.02026-06-25—无用户可见变更57.0.12026-07-15—无用户可见变更当前仓库中 package.json 锁定版本即57.0.1在 56.0.0 与 57.0.0 版本条目上各有一个值得注意的无声升级changelog 声明其未引入任何用户可见变更This version does not introduce any user-facing changes.说明这两次大版本号跃迁对应的是 SDK 主版本节奏下的整体对齐而非该模块自身的 API 或行为调整。换句话说对于恰好停留在这两个边界版本上的开发者升级本身是低风险的。0.1.9async后缀——一次全量 API 重命名的破坏性变更版本 0.1.92025-10-20的破坏性变更只有一条但影响面覆盖整个公开 API 面Rename functions to includeasyncsuffix.函数重命名以包含async后缀对比今日的 ExpoAppIntegrity.types.ts可以看到重命名后的最终形态——所有异步方法都统一带上了Async后缀并返回Promise// iOS isSupported: boolean; generateKeyAsync(): Promisestring; attestKeyAsync(keyId: string, challenge: string): Promisestring; generateAssertionAsync(keyId: string, challenge: string): Promisestring; // Android – Play Integrity prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promisevoid; requestIntegrityCheckAsync(requestHash: string): Promisestring; // Android – Hardware Attestation isHardwareAttestationSupportedAsync(): Promiseboolean; generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promisevoid; getAttestationCertificateChainAsync(keyAlias: string): Promisestring[];这一变更的意义有二其一与expo-modules-core生态中异步函数以async命名的约定对齐让调用方从函数名即可判断调用会跨 JSI / Promise 边界其二为后续新能力如硬件认证三件套的命名确立了统一规范——AppIntegrity.ts 中 0.1.9 之后新增的全部方法都严格遵循xxxAsync命名。从源码结构看旧名如attestKey在被调用时因找不到NativeModule上对应方法而直接抛错因此任何沿用旧 API 的存量代码在升级到 0.1.9 后都需要同步改名。0.1.7iOS 二进制数据的 Base64 修复——一个典型的编码回归版本 0.1.72025-09-11修复的问题在原生层有非常清晰的证据链[iOS] FixedattestKeyandgenerateAssertionmethods failing due to improper UTF-8 decoding of binary data. Now returns base64-encoded strings as expected.修复因二进制数据被不当按 UTF-8 解码而失败的问题现按要求返回 base64 编码字符串看 IntegrityModule.swift 中的实现attestKey(_:clientDataHash:)与generateAssertion(_:clientDataHash:)返回的是二进制签名数据Data。该模块当前在 Swift 侧对返回值做了显式转换let result try await service.attestKey(key, clientDataHash: clientDataHash) return result.base64EncodedString()问题本质在于Data是任意字节序列若在修复前被桥接层当作 UTF-8 字符串直接解码遇到非 UTF-8 字节就会产生解码错误或数据损坏而跨语言桥接传递二进制安全的载体时标准做法就是把Data转成 base64 字符串。修复后attestKeyAsync与generateAssertionAsync的返回值语义被固定为base64 编码字符串调用方拿到后可直接上送自己的服务端做验签无需再做字节层面的还原。类型定义中两个方法均为Promisestring也与此一致。0.1.8Android 硬件认证——为安全发行版补充的一条能力版本 0.1.82025-09-22是模块能力版图的一次重要扩充[Android] Add Android hardware attestation support.之所以需要它是因为标准的 Play Integrity 流程依赖 Play 服务与 Play 商店而 GrapheneOS 等强调隐私的发行版默认不附带这些组件。硬件认证路径直接调用 Android Keystore 的密钥认证能力绕开了对 Play 的依赖。从 IntegrityModule.kt 的实现可以看到这一路径的三个步骤如何落地能力探测isHardwareAttestationSupported()尝试获取并加载AndroidKeyStore实例若 Keystore 不可访问则抛出异常并映射为ERR_APP_INTEGRITY_HARDWARE_ATTESTATION_NOT_SUPPORTED密钥生成generateHardwareAttestedKeyAsync(keyAlias, challenge)基于KeyGenParameterSpec.Builder在 AndroidKeyStore 中生成 EC 签名密钥PURPOSE_SIGN SHA-256 摘要并通过setAttestationChallenge(challenge.toByteArray())把服务端下发的随机挑战绑定进认证证书。实现中若 alias 已存在会先删除旧条目再重建保证每次调用都得到全新密钥对同时通过Build.VERSION.SDK_INT N的判断兜底旧系统证书链读取getAttestationCertificateChainAsync(keyAlias)从 Keystore 取出KeyStore.getCertificateChain把每个X509Certificate的 DER 编码做Base64.NO_WRAP编码后以字符串数组返回供服务端离线验证设备是否持有可信的硬件认证根。JS 层 AppIntegrity.ts 对这三个方法在非 Android 平台直接抛错only available on Android而对isHardwareAttestationSupportedAsync在非 Android 平台返回false——这是各方法中唯一一个跨平台降级而非抛错的探测型 API语义上更符合能力查询的定位。破坏性变更56.0.0 的最低系统版本门槛56.0.02026-05-05是除 0.1.9 之外另一次用户可见的破坏性变更影响的是构建与部署目标而非 API 形态Bumped minimum iOS/tvOS version to 16.4, macOS to 13.4.对使用该模块的工程意味着升级到 56.x 后Xcode 工程的IPHONEOS_DEPLOYMENT_TARGET/TVOS_DEPLOYMENT_TARGET至少需要 16.4macOS若用于 Catalyst 或 macOS 目标至少需要 13.4更低部署目标的 App 将无法编译或需要放弃升级。这类门槛通常与模块原生依赖本仓库中 iOS 侧依赖DeviceCheck/CryptoKit见 IntegrityModule.swift 头部 import的 API 可用性要求同步收紧。由于 changelog 明确该版本不引入用户可见行为变化此升级的风险主要集中在系统版本兼容性评估上。无用户可见变更版本段说明从 0.1.10 一直到 55.0.13再到 56.0.1 之后的全部版本共二十余个changelog 均标注This version does not introduce any user-facing changes.。这类条目对应的实际变更通常包括与上游 Expo SDK 依赖对齐、原生工程模板同步、内部重构与测试补充或 CI / 打包管线调整——它们不影响 JS 层 API 契约也不改变运行时行为。对依赖方而言这类版本可以放心跟随 SDK 整体升级若你正在做版本审计也可以据此把精力集中在 0.1.7 / 0.1.8 / 0.1.9 / 56.0.0 这四个真正有实质变更的版本上。从错误码体系看三套能力各自的失败模式changelog 未直接罗列错误码但版本演进中新增的每条能力都配有一套独立错误域。iOS 侧的常量定义在 IntegrityErrorCodes.swiftAndroid 侧在 IntegrityErrorCodes.kt。前者直接对映 AppleDCError的枚举iOSERR_APP_INTEGRITY_FEATURE_UNSUPPORTED设备不支持 App Attest、INVALID_INPUT、INVALID_KEY、SERVER_UNAVAILABLE、SYSTEM_FAILURE、UNKNOWN。Android Play IntegrityERR_APP_INTEGRITY_API_NOT_AVAILABLE、APP_NOT_INSTALLED、APP_UID_MISMATCH、CANNOT_BIND_SERVICE、CLIENT_TRANSIENT_ERROR、INVALID_PROJECT_NUMBER、GOOGLE_SERVER_UNAVAILABLE、PROVIDER_INVALID、INTERNAL_ERROR、NETWORK_ERROR、PLAY_SERVICES_NOT_FOUND、PLAY_SERVICES_OUTDATED、PLAY_STORE_NOT_FOUND、PLAY_STORE_OUTDATED、REQUEST_HASH_TOO_LONG、TOO_MANY_REQUESTS外加流程性错误CANCELLED与PROVIDER_NOT_PREPARED后者在未先调用prepareIntegrityTokenProviderAsync就直接请求检查时抛出错误消息会明确提示调用顺序。Android 硬件认证HARDWARE_ATTESTATION_NOT_SUPPORTED、HARDWARE_ATTESTATION_KEY_GENERATION_FAILED、HARDWARE_ATTESTATION_FAILED、HARDWARE_ATTESTATION_CERTIFICATE_CHAIN_INVALID。从 IntegrityModule.kt 的实现可以看到错误映射的细节Play Integrity 的StandardIntegrityException.errorCode会被逐一映射为上述常量而硬件认证路径则是通过对异常消息做关键词匹配如包含not supported、key generation、certificate来归类到对应错误域。因此集成方在服务端或监控侧看到以ERR_APP_INTEGRITY_开头的错误码时可以先按平台二分定位失败环节。在真实项目中的安装与验证方式无论你关注的是该 changelog 的哪个版本接入方式都一致。README见 README.md给出的安装命令是npx expo install expo/app-integrity在托管managedExpo 工程中使用时请以 Expo 文档的安装指引为准该命令本身会自动选择与当前 SDK 匹配的版本在裸 React Native 工程中使用前需先完成expo包及expo-modules-core的安装配置。本模块以expo与react-native为 peer 依赖见 package.json自身不含第三方运行时依赖。若要快速验证 API 契约是否符合 changelog 描述仓库自带两层可读证据测试ExpoAppIntegrity-test.native.ts 覆盖了generateKeyAsync/attestKeyAsync/generateAssertionAsync/requestIntegrityCheckAsync/prepareIntegrityTokenProviderAsync的调用约定例如验证attestKeyAsync(key, challenge)以(key, challenge)顺序透传、prepareIntegrityTokenProviderAsync成功时解析为undefinedMockmocks/ExpoAppIntegrity.ts 为 Jest 环境提供与真实模块同名的自动 mock返回mock-key、mock-attestation等使不依赖真机硬件的前端逻辑也能被测试。注意该 mock 目前只覆盖 0.1.7 / 0.1.8 之前的五个方法尚未同步硬件认证三件套从源码结构看可以推断它仍在随模块主版本演进。小结与升级决策参考把 CHANGELOG.md 与源码对照阅读可以得到一份清晰的升级判断清单若你仍在使用 0.1.8 或更早版本必须先处理 0.1.9 的全量async重命名API 破坏性最大的一次并建议同时吸收 0.1.8 加入的 Android 硬件认证 API 与 0.1.7 的 iOS base64 修复若你处于 55.x 及以下升到 56.x 的唯一硬门槛是确认工程最低系统版本达到 iOS/tvOS 16.4、macOS 13.4API 层无需改动56.0.1 至 57.0.1 之间各版本均无用户可见变更可作为低风险跟随区间。该模块在 Expo SDK 中负责的是信任边界问题——无论用 App Attest、Play Integrity 还是硬件认证核心思路都是把设备侧的证明材料交给你自己的服务端去验证而非在客户端自证。本文所还原的每一次变更都在收敛这条信任链的可用性与正确性。延伸阅读仓库内相关文件版本记录原文packages/expo-app-integrity/CHANGELOG.mdJS 公开 API 与平台守卫packages/expo-app-integrity/src/AppIntegrity.ts原生方法签名类型packages/expo-app-integrity/src/ExpoAppIntegrity.types.tsiOS 原生实现与错误映射packages/expo-app-integrity/ios/IntegrityModule.swift、packages/expo-app-integrity/ios/IntegrityErrorCodes.swiftAndroid 原生实现Play Integrity 硬件认证packages/expo-app-integrity/android/src/main/java/expo/modules/integrity/IntegrityModule.kt、packages/expo-app-integrity/android/src/main/java/expo/modules/integrity/IntegrityErrorCodes.kt单元测试与 Jest mockpackages/expo-app-integrity/src/tests/ExpoAppIntegrity-test.native.ts、packages/expo-app-integrity/mocks/ExpoAppIntegrity.ts【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考