mobile-security-react-native
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Native Security Patterns
React Native 安全实践模式
Quick Guide: Defense-in-depth: layer secure storage (expo-secure-store or react-native-keychain), certificate pinning, biometric authentication, jailbreak/root detection, and code obfuscation. Never store secrets in AsyncStorage or JS bundles. Use Hermes bytecode as your first obfuscation layer. iOS Keychain persists across reinstalls; Android Keystore does not. Certificate pins require at least two hashes (primary + backup) on iOS.
<critical_requirements>
快速指南: 纵深防御:分层部署安全存储(expo-secure-store 或 react-native-keychain)、证书固定、生物识别认证、越狱/ROOT检测以及代码混淆。切勿在AsyncStorage或JS包中存储敏感信息。将Hermes字节码作为你的第一层混淆方案。iOS Keychain中的数据在应用重装后仍会保留;而Android Keystore则不会。iOS平台的证书固定要求至少提供两个哈希值(主哈希+备用哈希)。
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用本方案前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST NEVER store tokens, passwords, API keys, or PII in AsyncStorage or plain-text files -- use hardware-backed secure storage)
(You MUST use at least two public key hashes for certificate pinning on iOS -- TrustKit/iOS enforces this and will throw if only one is provided)
(You MUST treat jailbreak/root detection as one layer in defense-in-depth -- client-side checks can be bypassed, always validate server-side too)
(You MUST configure both iOS ATS and Android Network Security Config to enforce HTTPS -- never ship with in production)
NSAllowArbitraryLoads: true(You MUST add to Info.plist when using Face ID -- the OS silently falls back to passcode without it)
NSFaceIDUsageDescription</critical_requirements>
Auto-detection: secure storage, SecureStore, expo-secure-store, react-native-keychain, Keychain, Keystore, certificate pinning, SSL pinning, react-native-ssl-public-key-pinning, TrustKit, jailbreak detection, root detection, jail-monkey, biometric authentication, expo-local-authentication, Face ID, Touch ID, fingerprint, code obfuscation, Hermes bytecode, ProGuard, R8, screen capture prevention, App Transport Security, Network Security Config, MITM
When to use:
- Storing credentials, tokens, or sensitive data on device
- Implementing certificate pinning to prevent MITM attacks
- Adding biometric authentication (Face ID, Touch ID, fingerprint)
- Detecting jailbroken/rooted devices
- Hardening builds with code obfuscation (Hermes, ProGuard/R8)
- Preventing screenshot/screen recording of sensitive screens
- Configuring network security (ATS on iOS, Network Security Config on Android)
When NOT to use:
- General React Native component architecture (not a security concern)
- Server-side API security (use your backend security approach)
- Web-only applications (web security patterns differ fundamentally)
Key patterns covered:
- Secure storage with expo-secure-store and react-native-keychain
- Certificate pinning with react-native-ssl-public-key-pinning
- Biometric authentication with expo-local-authentication and react-native-keychain
- Jailbreak/root detection with jail-monkey
- Code obfuscation: Hermes bytecode, Metro transformer, ProGuard/R8
- Network security: iOS ATS and Android Network Security Config
- Screenshot and screen recording prevention
- Defense-in-depth strategy and security layering
Detailed Resources:
- examples/core.md - Secure storage, certificate pinning, biometric auth
- examples/hardening.md - Code obfuscation, jailbreak detection, screenshot prevention, network config
- reference.md - Security checklist, library API reference, pin hash commands
<philosophy>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、、命名常量)import type
(严禁在AsyncStorage或明文文件中存储令牌、密码、API密钥或个人身份信息——请使用硬件级安全存储)
(iOS平台的证书固定必须至少使用两个公钥哈希值——TrustKit/iOS强制执行此规则,若仅提供一个会抛出错误)
(必须将越狱/ROOT检测视为纵深防御的其中一层——客户端检测可被绕过,务必同时进行服务端验证)
(必须配置iOS ATS和Android网络安全配置以强制使用HTTPS——生产环境中绝不能设置)
NSAllowArbitraryLoads: true(使用Face ID时必须在Info.plist中添加——若无此项,系统会自动 fallback 到密码验证,且无任何提示)
NSFaceIDUsageDescription</critical_requirements>
自动检测范围: secure storage、SecureStore、expo-secure-store、react-native-keychain、Keychain、Keystore、certificate pinning、SSL pinning、react-native-ssl-public-key-pinning、TrustKit、越狱检测、ROOT检测、jail-monkey、生物识别认证、expo-local-authentication、Face ID、Touch ID、指纹识别、代码混淆、Hermes bytecode、ProGuard、R8、截图防护、App Transport Security、Network Security Config、MITM
适用场景:
- 在设备上存储凭证、令牌或敏感数据
- 实现证书固定以防止MITM攻击
- 添加生物识别认证(Face ID、Touch ID、指纹识别)
- 检测越狱/ROOT设备
- 通过代码混淆(Hermes、ProGuard/R8)强化构建安全性
- 防止敏感界面被截图/录屏
- 配置网络安全(iOS ATS、Android网络安全配置)
不适用场景:
- 通用React Native组件架构设计(非安全相关需求)
- 服务端API安全(请使用后端安全方案)
- 纯Web应用(Web安全实践与移动端存在本质差异)
涵盖的核心方案:
- 基于expo-secure-store和react-native-keychain的安全存储
- 基于react-native-ssl-public-key-pinning的证书固定
- 基于expo-local-authentication和react-native-keychain的生物识别认证
- 基于jail-monkey的越狱/ROOT检测
- 代码混淆:Hermes字节码、Metro转换器、ProGuard/R8
- 网络安全:iOS ATS与Android网络安全配置
- 截图与录屏防护
- 纵深防御策略与安全分层
详细资源:
- examples/core.md - 安全存储、证书固定、生物识别认证
- examples/hardening.md - 代码混淆、越狱检测、截图防护、网络配置
- reference.md - 安全检查清单、库API参考、哈希值生成命令
<philosophy>
Philosophy
安全理念
Mobile security is defense-in-depth -- no single measure is sufficient. Attackers can bypass any individual protection, so layer multiple defenses: secure storage protects data at rest, certificate pinning protects data in transit, biometric authentication protects access, jailbreak detection identifies compromised environments, and code obfuscation raises the cost of reverse engineering.
Core principles:
- Never trust the client -- all sensitive operations need server-side validation. Client-side checks are speed bumps, not walls.
- Hardware-backed storage -- iOS Keychain and Android Keystore provide hardware-level encryption. AsyncStorage is a plain-text file.
- HTTPS everywhere -- enforce TLS for all network communication. Certificate pinning adds a second layer against compromised CAs.
- Minimal data exposure -- store the least sensitive data possible on device. Prefer short-lived tokens over long-lived credentials.
- Fail secure -- when security checks fail (biometric, jailbreak), deny access by default rather than falling back to insecure paths.
Mental model:
Think of mobile security as concentric rings. Each ring (secure storage, pinning, biometrics, obfuscation, jailbreak detection) independently slows attackers. The combination creates a security posture that makes exploitation impractical for most threat models.
Platform differences that matter:
| Concern | iOS | Android |
|---|---|---|
| Secure storage | Keychain (persists across reinstalls) | Keystore + SharedPreferences (cleared on uninstall) |
| Biometrics | Face ID / Touch ID | Fingerprint / Face Unlock (weak vs strong) |
| Network security | ATS (default HTTPS since iOS 9) | Network Security Config (clear text blocked API 28+) |
| Code protection | Hermes bytecode (no ProGuard for JS) | Hermes bytecode + ProGuard/R8 for native/Java |
| Screenshot prevention | Effective (screen recording + screenshots) | FLAG_SECURE (effective for screenshots, partial for recording) |
<patterns>
移动安全遵循纵深防御原则——单一防护措施不足以抵御攻击。攻击者可绕过任何单独的防护手段,因此需部署多层防御:安全存储保护静态数据,证书固定保护传输中的数据,生物识别认证保护访问权限,越狱检测识别受 compromise 的环境,代码混淆提高逆向工程的成本。
核心原则:
- 绝不信任客户端——所有敏感操作都需要服务端验证。客户端检查只是延缓攻击的手段,而非绝对屏障。
- 硬件级存储——iOS Keychain和Android Keystore提供硬件级加密。AsyncStorage本质是明文文件。
- 全面使用HTTPS——强制所有网络通信使用TLS。证书固定为受 compromise 的CA提供第二层防护。
- 最小化数据暴露——在设备上存储尽可能少的敏感数据。优先使用短期令牌而非长期凭证。
- 安全失败默认——当安全检查失败(生物识别、越狱检测)时,默认拒绝访问,而非 fallback 到不安全路径。
思维模型:
将移动安全视为同心圆。每一层(安全存储、证书固定、生物识别、混淆、越狱检测)都能独立延缓攻击者的步伐。多层组合形成的安全态势,对大多数威胁模型而言,攻击成本高到难以实施。
需关注的平台差异:
| 关注点 | iOS | Android |
|---|---|---|
| 安全存储 | Keychain(应用重装后数据仍保留) | Keystore + SharedPreferences(卸载后数据清除) |
| 生物识别 | Face ID / Touch ID | 指纹识别 / 面部解锁(区分弱验证与强验证) |
| 网络安全 | ATS(iOS 9起默认强制HTTPS) | Network Security Config(API 28起默认阻止明文通信) |
| 代码保护 | Hermes字节码(JS代码无需ProGuard) | Hermes字节码 + ProGuard/R8(针对原生/Java代码) |
| 截图防护 | 效果良好(可阻止截图与录屏) | FLAG_SECURE(截图防护可靠,录屏防护效果因版本而异) |
<patterns>
Core Patterns
核心方案
Pattern 1: Secure Storage
方案1:安全存储
Two main libraries: expo-secure-store (Expo-managed, simpler API, 2KB value limit) and react-native-keychain (bare RN, biometric-protected credentials, no size limit).
expo-secure-store uses iOS Keychain and Android Keystore-encrypted SharedPreferences. Values are strings with a ~2KB limit. Supports biometric gating via .
requireAuthenticationtypescript
import * as SecureStore from "expo-secure-store";
const AUTH_TOKEN_KEY = "auth-token";
// Store securely with biometric protection
await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token, {
requireAuthentication: true,
authenticationPrompt: "Authenticate to save credentials",
});
// Retrieve (prompts biometric if requireAuthentication was set)
const stored = await SecureStore.getItemAsync(AUTH_TOKEN_KEY);react-native-keychain provides credential storage with granular access control and biometric gating via options.
ACCESS_CONTROLtypescript
import * as Keychain from "react-native-keychain";
await Keychain.setGenericPassword("user@example.com", token, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});Why good: hardware-backed encryption, biometric gating prevents unauthorized reads, prevents extraction from backups
WHEN_UNLOCKED_THIS_DEVICE_ONLYSee examples/core.md for full secure storage patterns with error handling and library comparison.
主要有两个库:expo-secure-store(Expo托管环境,API更简洁,值大小限制2KB)和react-native-keychain(原生RN环境,支持生物识别保护的凭证存储,无大小限制)。
expo-secure-store 使用iOS Keychain和Android Keystore加密的SharedPreferences。存储值为字符串,约2KB限制。支持通过启用生物识别验证。
requireAuthenticationtypescript
import * as SecureStore from "expo-secure-store";
const AUTH_TOKEN_KEY = "auth-token";
// 启用生物识别保护的安全存储
await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token, {
requireAuthentication: true,
authenticationPrompt: "验证身份以保存凭证",
});
// 读取(若设置了requireAuthentication会触发生物识别验证)
const stored = await SecureStore.getItemAsync(AUTH_TOKEN_KEY);react-native-keychain 提供凭证存储功能,支持细粒度访问控制,可通过选项启用生物识别验证。
ACCESS_CONTROLtypescript
import * as Keychain from "react-native-keychain";
await Keychain.setGenericPassword("user@example.com", token, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});优势: 硬件级加密,生物识别验证防止未授权读取,可防止从备份中提取数据
WHEN_UNLOCKED_THIS_DEVICE_ONLY完整的安全存储方案(含错误处理与库对比)请查看examples/core.md。
Pattern 2: Certificate Pinning
方案2:证书固定
Pin your server's public key hashes to prevent MITM attacks even when a device's CA store is compromised. Use react-native-ssl-public-key-pinning for a JS-level approach that works with all HTTP clients.
typescript
import { initializeSslPinning } from "react-native-ssl-public-key-pinning";
const PIN_EXPIRATION_DATE = "2026-12-31";
await initializeSslPinning({
"api.example.com": {
includeSubdomains: true,
publicKeyHashes: [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Primary
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Backup (REQUIRED on iOS)
],
expirationDate: PIN_EXPIRATION_DATE,
},
});Why good: intercepts all fetch/XMLHttpRequest calls globally, no native code changes required, expiration date prevents bricking when certificates rotate
Gotcha: iOS requires at least two hashes per domain. Providing only one causes to throw.
initializeSslPinningSee examples/core.md for rotation strategy and reference.md for pin hash generation commands.
固定服务器的公钥哈希值,即使设备CA存储被 compromise,也能防止MITM攻击。使用react-native-ssl-public-key-pinning实现JS级固定,兼容所有HTTP客户端。
typescript
import { initializeSslPinning } from "react-native-ssl-public-key-pinning";
const PIN_EXPIRATION_DATE = "2026-12-31";
await initializeSslPinning({
"api.example.com": {
includeSubdomains: true,
publicKeyHashes: [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // 主哈希
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // 备用哈希(iOS平台必填)
],
expirationDate: PIN_EXPIRATION_DATE,
},
});优势: 全局拦截所有fetch/XMLHttpRequest请求,无需修改原生代码,过期日期设置可避免证书轮换导致应用无法使用
注意事项: iOS平台每个域名至少需要两个哈希值。若仅提供一个,会抛出错误。
initializeSslPinning证书轮换策略请查看examples/core.md,哈希值生成命令请查看reference.md。
Pattern 3: Biometric Authentication
方案3:生物识别认证
Two approaches: expo-local-authentication (standalone biometric prompt, no credential storage) and react-native-keychain (biometric-gated credential retrieval).
typescript
import * as LocalAuthentication from "expo-local-authentication";
async function authenticateUser(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) return false;
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "Verify your identity",
disableDeviceFallback: false,
cancelLabel: "Cancel",
});
return result.success;
}Why good: checks hardware + enrollment before prompting, disableDeviceFallback: false allows passcode as backup, clean boolean result
Gotcha: Face ID requires in Info.plist. Without it, iOS silently falls back to passcode (no error, no Face ID prompt). Face ID is not supported in Expo Go -- use a development build.
NSFaceIDUsageDescriptionSee examples/core.md for biometric-gated credential flow combining both libraries.
两种实现方式:expo-local-authentication(独立生物识别弹窗,不涉及凭证存储)和react-native-keychain(生物识别验证后的凭证读取)。
typescript
import * as LocalAuthentication from "expo-local-authentication";
async function authenticateUser(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) return false;
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "验证你的身份",
disableDeviceFallback: false,
cancelLabel: "取消",
});
return result.success;
}优势: 验证前先检查硬件支持与已注册状态,disableDeviceFallback: false允许密码作为备用验证方式,返回结果为清晰的布尔值
注意事项: 使用Face ID时必须在Info.plist中添加。若无此项,iOS会自动 fallback 到密码验证,且无任何错误提示。Face ID在Expo Go中不支持——请使用开发构建版本。
NSFaceIDUsageDescription结合两个库的生物识别验证凭证流程请查看examples/core.md。
Pattern 4: Jailbreak/Root Detection
方案4:越狱/ROOT检测
Detect compromised devices where security controls (sandboxing, code signing) are disabled. Use jail-monkey for detection checks.
typescript
import JailMonkey from "jail-monkey";
function getDeviceSecurityStatus() {
return {
isJailbroken: JailMonkey.isJailBroken(),
canMockLocation: JailMonkey.canMockLocation(),
isDebugMode: JailMonkey.isDebuggedMode(),
isOnExternalStorage: JailMonkey.isOnExternalStorage(), // Android only
};
}Why good: checks multiple indicators (not just one file path), includes location mocking and debug detection
Important: Jailbreak detection is a speed bump, not a wall. Determined attackers bypass client-side checks with hooking frameworks (Frida, Objection). Always pair with server-side device attestation for high-security apps.
See examples/hardening.md for response strategies and server-side validation.
检测安全控制(沙箱、代码签名)已被禁用的受 compromise 设备。使用jail-monkey进行检测。
typescript
import JailMonkey from "jail-monkey";
function getDeviceSecurityStatus() {
return {
isJailbroken: JailMonkey.isJailBroken(),
canMockLocation: JailMonkey.canMockLocation(),
isDebugMode: JailMonkey.isDebuggedMode(),
isOnExternalStorage: JailMonkey.isOnExternalStorage(), // 仅Android
};
}优势: 检测多个指标(而非单一文件路径),包含位置模拟与调试模式检测
重要提示: 越狱检测只是延缓攻击的手段,而非绝对屏障。有决心的攻击者可通过Hook框架(Frida、Objection)绕过客户端检测。高安全级别应用务必搭配服务端设备验证。
响应策略与服务端验证请查看examples/hardening.md。
Pattern 5: Code Obfuscation
方案5:代码混淆
Layer 1: Hermes bytecode (enabled by default since RN 0.70) compiles JS to bytecode, making casual reverse engineering difficult. Layer 2: Metro obfuscation transformer for additional string encryption and control flow flattening. Layer 3: ProGuard/R8 for Android native/Java code.
javascript
// metro.config.js -- adding obfuscation transformer
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve("react-native-obfuscating-transformer"),
};
module.exports = config;Why good: layered approach -- Hermes handles baseline, transformer adds string/flow obfuscation, ProGuard/R8 covers native code
Gotcha: Not all obfuscation options work. The option in react-native-obfuscating-transformer is known to break builds. Test thoroughly.
stringArraySee examples/hardening.md for ProGuard/R8 configuration and obfuscation options.
第一层:Hermes字节码(RN 0.70起默认启用)将JS编译为字节码,增加普通逆向工程的难度。第二层:Metro混淆转换器实现额外的字符串加密与控制流扁平化。第三层:ProGuard/R8用于Android原生/Java代码混淆。
javascript
// metro.config.js -- 添加混淆转换器
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve("react-native-obfuscating-transformer"),
};
module.exports = config;优势: 分层防护——Hermes提供基础防护,转换器增加字符串/控制流混淆,ProGuard/R8覆盖原生代码
注意事项: 并非所有混淆选项都可用。react-native-obfuscating-transformer中的选项会导致构建失败,请避免使用。务必充分测试。
stringArrayProGuard/R8配置与混淆选项请查看examples/hardening.md。
Pattern 6: Network Security Configuration
方案6:网络安全配置
Enforce HTTPS at the OS level. iOS uses App Transport Security (ATS), Android uses Network Security Config.
iOS (Info.plist): ATS enforces HTTPS by default since iOS 9. Never ship with .
NSAllowArbitraryLoads: trueAndroid (network_security_config.xml): Clear text blocked by default since API 28. Pin certificates natively for defense-in-depth alongside JS-level pinning.
xml
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">AAAAAAAAAA...=</pin>
<pin digest="SHA-256">BBBBBBBBBB...=</pin>
</pin-set>
</domain-config>
</network-security-config>Why good: OS-level enforcement, clear text blocked globally, native pinning adds second layer beyond JS-level pinning
See examples/hardening.md for iOS ATS configuration and debug vs release network policies.
在系统层面强制使用HTTPS。iOS使用App Transport Security(ATS),Android使用Network Security Config。
iOS(Info.plist): iOS 9起ATS默认强制使用HTTPS。生产环境绝不能设置。
NSAllowArbitraryLoads: trueAndroid(network_security_config.xml): API 28起默认阻止明文通信。配合JS级固定,在系统层面添加证书固定以实现纵深防御。
xml
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">AAAAAAAAAA...=</pin>
<pin digest="SHA-256">BBBBBBBBBB...=</pin>
</pin-set>
</domain-config>
</network-security-config>优势: 系统层面强制执行,全局阻止明文通信,原生固定为JS级固定提供第二层防护
iOS ATS配置与调试/发布环境网络策略请查看examples/hardening.md。
Pattern 7: Screenshot and Screen Recording Prevention
方案7:截图与录屏防护
Prevent screen capture on sensitive screens (banking, credentials, personal data).
typescript
import { useIsFocused } from "@react-navigation/native";
import * as ScreenCapture from "expo-screen-capture";
import { useEffect } from "react";
function usePreventCapture() {
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
ScreenCapture.preventScreenCaptureAsync();
} else {
ScreenCapture.allowScreenCaptureAsync();
}
return () => {
ScreenCapture.allowScreenCaptureAsync();
};
}, [isFocused]);
}Why good: per-screen control (not global), cleanup on unfocus/unmount, works for both screenshots and screen recording on iOS
Gotcha: On Android, reliably blocks screenshots but screen recording prevention varies by Android version. On iOS, screenshot content is replaced with blank but the user can still trigger the screenshot action.
FLAG_SECURESee examples/hardening.md for non-Expo alternatives and listener-based approaches.
</patterns>
<decision_framework>
防止敏感界面(银行、凭证、个人数据)被截图或录屏。
typescript
import { useIsFocused } from "@react-navigation/native";
import * as ScreenCapture from "expo-screen-capture";
import { useEffect } from "react";
function usePreventCapture() {
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
ScreenCapture.preventScreenCaptureAsync();
} else {
ScreenCapture.allowScreenCaptureAsync();
}
return () => {
ScreenCapture.allowScreenCaptureAsync();
};
}, [isFocused]);
}优势: 支持按界面控制(而非全局),界面失焦/卸载时自动恢复,iOS平台可同时阻止截图与录屏
注意事项: Android平台上,可可靠阻止截图,但录屏防护效果因Android版本而异。iOS平台上,截图内容会被替换为空白,但用户仍可触发截图操作。
FLAG_SECURE非Expo替代方案与基于监听器的实现请查看examples/hardening.md。
</patterns>
<decision_framework>
Decision Framework
决策框架
Secure Storage Choice
安全存储选择
Need to store credentials/tokens securely?
+-- Using Expo managed workflow?
| +-- YES -> expo-secure-store (simpler API, Expo-native)
| +-- NO -> react-native-keychain (more control, biometric options)
|
+-- Need biometric-gated credential retrieval?
| +-- YES -> react-native-keychain with ACCESS_CONTROL.BIOMETRY_ANY
| +-- OR -> expo-secure-store with requireAuthentication: true
|
+-- Value larger than 2KB?
| +-- YES -> react-native-keychain (no size limit)
| +-- NO -> Either library works
|
+-- Need credential persistence across app reinstalls?
+-- iOS -> Both persist (Keychain behavior)
+-- Android -> Neither persists (cleared on uninstall)需要安全存储凭证/令牌?
+-- 使用Expo托管工作流?
| +-- 是 -> expo-secure-store(API更简洁,原生Expo支持)
| +-- 否 -> react-native-keychain(控制更灵活,支持生物识别选项)
|
+-- 需要生物识别验证的凭证读取?
| +-- 是 -> react-native-keychain搭配ACCESS_CONTROL.BIOMETRY_ANY
| +-- 或 -> expo-secure-store搭配requireAuthentication: true
|
+-- 值大小超过2KB?
| +-- 是 -> react-native-keychain(无大小限制)
| +-- 否 -> 两个库均可
|
+-- 需要凭证在应用重装后保留?
+-- iOS -> 两个库均可(Keychain特性)
+-- Android -> 两个库均不可(卸载后数据清除)Biometric Authentication Choice
生物识别认证选择
Need biometric prompt (no credential storage)?
+-- YES -> expo-local-authentication
|
Need biometric-gated credential storage/retrieval?
+-- YES -> react-native-keychain with accessControl
|
Need to distinguish biometric security level (weak vs strong)?
+-- YES -> expo-local-authentication (provides SecurityLevel enum)仅需要生物识别弹窗(不涉及凭证存储)?
+-- 是 -> expo-local-authentication
|
需要生物识别验证的凭证存储/读取?
+-- 是 -> react-native-keychain搭配accessControl
|
需要区分生物识别安全级别(弱验证vs强验证)?
+-- 是 -> expo-local-authentication(提供SecurityLevel枚举)Certificate Pinning Approach
证书固定方案选择
Need SSL pinning?
+-- JS-level (works with all HTTP clients)?
| +-- YES -> react-native-ssl-public-key-pinning
|
+-- Native-level (defense-in-depth)?
| +-- iOS -> TrustKit (via CocoaPods)
| +-- Android -> Network Security Config XML
|
+-- Best practice -> Both JS-level AND native-level需要SSL固定?
+-- JS级固定(兼容所有HTTP客户端)?
| +-- 是 -> react-native-ssl-public-key-pinning
|
+-- 原生级固定(纵深防御)?
| +-- iOS -> TrustKit(通过CocoaPods)
| +-- Android -> Network Security Config XML
|
+-- 最佳实践 -> 同时使用JS级与原生级固定Security Layering
安全分层
Minimum viable security:
1. Secure storage (expo-secure-store or react-native-keychain)
2. HTTPS enforcement (ATS + Network Security Config)
3. Hermes bytecode (default since RN 0.70)
Standard security (most apps):
+ Certificate pinning
+ Biometric authentication
+ ProGuard/R8 on Android
High security (banking, healthcare, fintech):
+ Jailbreak/root detection
+ JS code obfuscation transformer
+ Screenshot prevention
+ Server-side device attestation
+ Runtime integrity checks</decision_framework>
<red_flags>
最低安全要求:
1. 安全存储(expo-secure-store或react-native-keychain)
2. HTTPS强制(ATS + Network Security Config)
3. Hermes字节码(RN 0.70起默认启用)
标准安全配置(大多数应用):
+ 证书固定
+ 生物识别认证
+ Android平台ProGuard/R8
高安全配置(银行、医疗、金融科技):
+ 越狱/ROOT检测
+ JS代码混淆转换器
+ 截图防护
+ 服务端设备验证
+ 运行时完整性检查</decision_framework>
<red_flags>
RED FLAGS
风险警示
High Priority Issues:
- Storing tokens or credentials in AsyncStorage -- it is a plain-text file, trivially readable on jailbroken/rooted devices
- Storing API keys or secrets in the JS bundle -- the bundle is extractable from any published app
- Shipping with in production Info.plist -- disables ATS entirely, allows HTTP
NSAllowArbitraryLoads: true - Using only one public key hash for certificate pinning on iOS -- TrustKit throws, pinning silently fails
- Relying solely on jailbreak detection for security -- client-side checks are bypassable with Frida/Objection
Medium Priority Issues:
- Not checking and
hasHardwareAsyncbefore callingisEnrolledAsync-- crashes or confusing UX on devices without biometricsauthenticateAsync - Missing in Info.plist -- Face ID silently falls back to passcode without any error
NSFaceIDUsageDescription - Using for Keychain items -- allows access even when device is locked (deprecated on iOS)
ACCESSIBLE.ALWAYS - Skipping ProGuard/R8 on Android release builds -- native code is trivially decompilable without it
- Not setting on certificate pins -- expired certificates brick the app until users update
expirationDate
Gotchas & Edge Cases:
- iOS Keychain data persists across app reinstalls (same bundle ID); Android Keystore data does not -- plan token refresh accordingly
- expo-secure-store has a ~2KB value size limit -- large tokens or data blobs will silently fail or throw
- treats string
z.coerce.boolean()as truthy -- when parsing security config from strings, use explicit comparison"false" - Android blocks screenshots reliably but screen recording prevention varies by OS version and manufacturer
FLAG_SECURE - Hermes bytecode can be decompiled with tools like -- it raises the bar but is not true encryption
hbctool - react-native-obfuscating-transformer's option breaks React Native builds -- avoid it
stringArray - Android biometrics have "weak" (2D face) vs "strong" (fingerprint, 3D face) security levels -- financial apps should require strong
- Certificate pins must be rotated before expiry -- set calendar reminders and use the field as a safety net
expirationDate - invalidates credentials when biometrics are re-enrolled -- use
Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SETfor persistence across biometric changesBIOMETRY_ANY
</red_flags>
<critical_reminders>
高优先级问题:
- 在AsyncStorage中存储令牌或凭证——本质是明文文件,越狱/ROOT设备可轻易读取
- 在JS包中存储API密钥或敏感信息——任何已发布的应用均可提取包内容
- 生产环境Info.plist中设置——完全禁用ATS,允许HTTP通信
NSAllowArbitraryLoads: true - iOS平台证书固定仅使用一个公钥哈希值——TrustKit会抛出错误,固定功能静默失效
- 仅依赖越狱检测实现安全——客户端检测可被Frida/Objection绕过
中优先级问题:
- 调用前未检查
authenticateAsync和hasHardwareAsync——无生物识别设备上会崩溃或出现糟糕的用户体验isEnrolledAsync - Info.plist中缺少——Face ID会自动 fallback 到密码验证,且无任何提示
NSFaceIDUsageDescription - Keychain项使用——设备锁定时仍可访问(iOS已废弃)
ACCESSIBLE.ALWAYS - Android发布构建未启用ProGuard/R8——原生代码可轻易反编译
- 证书固定未设置——证书过期会导致应用无法使用,需用户更新
expirationDate
注意事项与边缘情况:
- iOS Keychain数据在应用重装后仍保留(相同Bundle ID);Android Keystore数据则不会——需相应规划令牌刷新策略
- expo-secure-store有~2KB的值大小限制——大令牌或数据块会静默失败或抛出错误
- 会将字符串
z.coerce.boolean()视为真值——从字符串解析安全配置时,请使用显式比较"false" - Android 可可靠阻止截图,但录屏防护效果因系统版本与厂商而异
FLAG_SECURE - Hermes字节码可被等工具反编译——仅能提高攻击门槛,并非真正加密
hbctool - react-native-obfuscating-transformer的选项会破坏React Native构建——请避免使用
stringArray - Android生物识别分为"弱"(2D面部)与"强"(指纹、3D面部)安全级别——金融应用应要求强验证
- 证书固定必须在过期前轮换——设置日历提醒,并使用字段作为安全保障
expirationDate - 会在生物识别重新注册后使凭证失效——若需跨生物识别变更保留凭证,请使用
Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SETBIOMETRY_ANY
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md
(You MUST NEVER store tokens, passwords, API keys, or PII in AsyncStorage or plain-text files -- use hardware-backed secure storage)
(You MUST use at least two public key hashes for certificate pinning on iOS -- TrustKit/iOS enforces this and will throw if only one is provided)
(You MUST treat jailbreak/root detection as one layer in defense-in-depth -- client-side checks can be bypassed, always validate server-side too)
(You MUST configure both iOS ATS and Android Network Security Config to enforce HTTPS -- never ship with in production)
NSAllowArbitraryLoads: true(You MUST add to Info.plist when using Face ID -- the OS silently falls back to passcode without it)
NSFaceIDUsageDescriptionFailure to follow these rules will expose user credentials, enable MITM attacks, and create false security assumptions.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目规范
(严禁在AsyncStorage或明文文件中存储令牌、密码、API密钥或个人身份信息——请使用硬件级安全存储)
(iOS平台的证书固定必须至少使用两个公钥哈希值——TrustKit/iOS强制执行此规则,若仅提供一个会抛出错误)
(必须将越狱/ROOT检测视为纵深防御的其中一层——客户端检测可被绕过,务必同时进行服务端验证)
(必须配置iOS ATS和Android网络安全配置以强制使用HTTPS——生产环境中绝不能设置)
NSAllowArbitraryLoads: true(使用Face ID时必须在Info.plist中添加——若无此项,系统会自动 fallback 到密码验证,且无任何提示)
NSFaceIDUsageDescription违反上述规则会暴露用户凭证、允许MITM攻击,并造成安全假象。
</critical_reminders>