@[toc]
React Native 在效率和跨平台上确实香,但安全这块如果不额外加固,几乎是"裸奔"。
很多团队一开始觉得:
App 是壳,逻辑在服务器
被反编译了也没啥大不了
结果上线后才发现:
- JS bundle 被直接扒出来
- API 被抓包,参数规则一清二楚
- 业务校验逻辑被逆向复刻
- 甚至直接被做成"外挂 App"
这篇文章就从RN 项目真实会被攻击的角度,系统讲一套可落地的安全强化方案。
RN 项目常见安全痛点
先说结论:RN 默认安全级别非常低。
1. JS bundle 明文存储
无论 Android 还是 iOS:
index.android.bundlemain.jsbundle
基本都是明文 JS 文件,稍微懂点的人:
bash
apktool d app.apk
或者直接解 IPA,就能看到完整业务逻辑。
2. API 请求太容易被抓
- HTTPS 证书没校验
- 参数规则固定
- Token 生成逻辑在 JS 里
Charles / Fiddler / mitmproxy 一开,API 路径、参数、返回结构一清二楚。
3. 混淆机制非常有限
Metro 的 minify ≠ 安全:
- 变量名可以还原
- 业务流程完全可读
- 关键算法一览无余
JS Bundle 加密方案,到底可不可行?
结论先给出来:可行,但只能作为第一道门槛。
基本思路
- JS bundle 不明文落盘
- Native 启动时解密
- 解密后的 JS 仅存在于内存中
架构示意
加密 bundle → 打包进 assets
↓
Native 启动
↓
Native 解密
↓
JS 引擎加载内存代码
Android 示例:AES 加密 Bundle(可运行)
1. 构建阶段加密 JS
bash
openssl enc -aes-256-cbc -salt \
-in index.android.bundle \
-out index.android.bundle.enc \
-k my_secret_key
打包时只放 .enc 文件。
2. Native 解密并加载(Android)
java
private byte[] decrypt(byte[] encrypted) throws Exception {
SecretKeySpec key = new SecretKeySpec(
"my_secret_key".getBytes(), "AES"
);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(encrypted);
}
解密完成后,直接丢给 RN 引擎加载。
安全收益
- 静态分析直接失效
- 拿到 APK 也看不到 JS
- 提高逆向成本
注意
这不是终极方案,只要解密逻辑在客户端,就一定能被 Hook。
API 安全:不要只靠 HTTPS
HTTPS 只是"防路人",不是"防攻击者"。
HMAC 签名:最基础也最有效
思路
每个请求都带一个签名:
text
sign = HMAC(secret, path + timestamp + body)
服务端验证签名合法性。
JS 侧示例
js
import CryptoJS from 'crypto-js';
function signRequest(path, body, ts) {
const raw = path + ts + JSON.stringify(body);
return CryptoJS.HmacSHA256(raw, SECRET).toString();
}
服务端校验
- 时间戳是否过期
- 签名是否一致
安全效果
- 参数被改,签名就不对
- 重放攻击直接失效
非对称签名:更高安全级别
强烈建议:签名逻辑放 Native
Native 生成签名
- 私钥存在 Keychain / Keystore
- JS 只能调用 Native 方法
RN 调用 Native 签名
js
const sign = await NativeSecurity.sign(payload);
JS 即使被扒,也拿不到私钥。
环境检测:越狱 / Root 一定要做
很多攻击,只发生在 Root / 越狱设备上。
Android Root 检测(示例)
java
public boolean isRooted() {
String[] paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su"
};
for (String path : paths) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
iOS 越狱检测(示例)
swift
func isJailBroken() -> Bool {
let paths = [
"/Applications/Cydia.app",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash"
]
return paths.contains { FileManager.default.fileExists(atPath: $0) }
}
检测到异常怎么办?
不要直接崩溃(容易被绕),可以:
- 限制功能
- 降级 API
- 关闭关键业务入口
- 后台记录风险设备
用原生增强安全,是 RN 的正确姿势
核心原则:安全逻辑尽量不写 JS。
适合放 Native 的内容:
- 加解密
- 签名
- Token 生成
- 设备指纹
- 风控策略
示例:Token 在 Native 生成
js
const token = await NativeSecurity.generateToken();
Native 内部:
- 读取设备信息
- 加盐
- 时间戳
- 签名
JS 永远拿不到规则细节。
注意点:千万不要依赖单一防护
这是最重要的一点。
任何单点防护 = 迟早被破。
推荐组合拳:
| 防护点 | 作用 |
|---|---|
| JS Bundle 加密 | 防静态分析 |
| Native 安全逻辑 | 防规则泄露 |
| API 签名 | 防抓包篡改 |
| Root/JB 检测 | 防 Hook |
| 服务端风控 | 最终兜底 |
实际场景:我们真实踩过的坑
场景 1:业务校验写在 JS
- 校验规则被扒
- 黑产直接写脚本刷接口
解决:校验移到 Native + 服务端二次校验
场景 2:Token 生成在 JS
- 算法被还原
- 请求被批量模拟
解决:Token 私钥放 Keystore,JS 只拿结果
场景 3:只做了混淆
- 三天被逆
- API 全暴露
解决:多层防护,而不是"我做了混淆就安全了"
总结
RN 项目安全,不能指望一个方案解决所有问题。
真正靠谱的思路是:
- 提高逆向成本
- 缩小攻击面
- 把关键逻辑移出 JS
- 用服务端兜底
记住一句话:
RN 的安全不是"防破解",而是"防滥用 + 防规模化攻击"