应用安全性能权衡
# 应用安全与性能权衡
📊 学习成本预估 | 难度:⭐⭐⭐⭐(4/5)| 阅读:约 40 分钟 | 实操:3 小时 🔗 前置阅读:全栏 | ➡️ 后续延伸:—
# 目录介绍
- 01.阅读说明
- 02.贯穿案例
- 03.安全思想原理
- 04.威胁模型分析
- 05.密码学第一性
- 06.度量与采集
- 07.归因决策树
- 08.传输安全全链路 ⭐
- 09.存储安全全链路 ⭐
- 10.代码保护全链路 ⭐
- 11.运行时防御全链路 ⭐
- 12.完整性校验全链路 ⭐
- 13.跨端安全对照
- 14.治理一层基线 ⭐
- 15.治理二层强化 ⭐
- 16.治理三层对抗 ⭐
- 17.求证实验 ⭐
- 18.实战案例
- 19.防劣化体系
- 20.跨平台速查
- 21.总结与延伸
# 01.阅读说明
- 本文卷归属:卷五 · 交付与防御 · 第 3 篇
- 本文目标层级:L3 专家 → L4 架构
- 适用平台:Android / iOS / Web / 嵌入式
- 前置阅读:
卷四·02 网络性能分析与优化(HTTPS 是网络与安全的交集) - 横联阅读:
卷五·01 崩溃捕获(崩溃常被用作攻击入口) - 本文核心命题:
安全是性能/稳定性的隐形成本,但选对方案两者并不矛盾。 安全 = 思想(成本对抗 / 纵深防御 / 最小权限)+ 原理(密码学 / PKI / TEE)+ 五道防线(传输 / 存储 / 代码 / 运行时 / 完整性)+ 三层治理(基线 / 强化 / 对抗)。 链条最弱一环决定整体安全——一个不加密的 API 让全应用 HTTPS 失去意义。
# 02.贯穿案例
本案例贯穿全文:§03 拿到思想、§04 拿到威胁模型、§05 拿到密码学第一性、§08-§12 拆解五道防线、§14-§16 三层治理、§17 用实验复盘。
# 2.1 案例背景
某中型电商 App V5.0 上线半年后接连发生三起安全事故,损失累计 200 万:
- 事故 A:用户优惠券接口未加签名,黑产用脚本批量领券薅羊毛 80 万。
- 事故 B:本地数据库(SharedPreferences 明文)保存了用户手机号与下单地址,被恶意 App 读走,公司被监管约谈、罚款 50 万。
- 事故 C:核心算法被反编译破解,竞品做出"一比一克隆",直接抢走 30% 用户。
研发组复盘:"我们用了 HTTPS 啊。"——这是经典的"以为做了一点就够了"。
# 2.2 经验派 8 周折腾
| 周次 | 动作 | 结果 |
|---|---|---|
| 第 1-2 周 | 把所有 API 都加 HTTPS(全站启用) | 事故 A 仍发生(HTTPS 不防业务伪造) |
| 第 3 周 | 给敏感字段做"AES 加密" | 密钥硬编码在代码里,反编译就能拿 |
| 第 4 周 | 上线最强 VMP 加固 | 启动慢 400ms,用户流失 5% |
| 第 5 周 | 加 Root 检测,发现就退出 | 国内厂商 ROM 被误判,10% 用户进不去 |
| 第 6 周 | 自实现"超级加密协议" | 上线一周即被破解(自实现密码学 = 必出错) |
| 第 7 周 | 上 Frida 检测脚本 | 攻击者改一行字节码绕过 |
| 第 8 周 | 接第三方"全方位安全 SDK" | SDK 启动加 200ms,仍未解决根本问题 |
复盘:八周折腾错在"以为安全是叠加单点工具"。真相是:安全不是工具堆叠,是基于威胁模型的体系化设计——经验派从未问过"我要防的是谁、有多大动机、用什么手段"。
# 2.3 方法派 14 天闭环
Day 1-2(§04 威胁模型):先识别真实威胁——业务伪造(薅羊毛)、数据泄露(合规)、反编译(IP 保护)。三种威胁的攻击者动机、能力、手段完全不同。
Day 3-4(§05 密码学第一性 + §14 基线):把基线做扎实——HTTPS + 双向 mTLS(关键 API)+ AES-GCM(系统 Keystore 存密钥)+ HMAC API 签名 + 时间戳防重放。所有密钥从系统密钥库派生,不入代码仓。
Day 5-7(§08-§12 五道防线 + §15 强化):分别加固传输(HSTS+证书 pinning)、存储(EncryptedSharedPreferences)、代码(R8 + 轻量加固)、运行时(Root 检测但仅风控不阻断)、完整性(包签名校验)。
Day 8-10(§16 对抗):核心算法(订单计费、风控规则)走 VMP 虚拟机保护,其他普通混淆即可——不是所有代码都需要最强保护。
Day 11-14(灰度 + §19 SLO 监控):上线弱网/低端机灰度 + 安全告警 SLO。
# 2.4 上线效果
| 指标 | 经验派 8 周后 | 方法派 14 天后 | 行业基准 |
|---|---|---|---|
| 业务伪造(薅羊毛) | 仍发生 | 0 起 | 0 起 |
| 数据泄露风险 | 高(明文存储) | 合规通过 | 合规 |
| 关键代码可破解 | 是 | VMP 阻挡 90% 攻击 | - |
| 启动时长 | 1.6s(VMP 全量) | 1.3s(精准 VMP) | < 1.5s |
| Root 用户误伤 | 10% | 0(不阻断仅风控) | 0 |
| 安全 audit 通过率 | 60% | 95% | > 90% |
核心洞察:"安全 = 加密+加固"是错的,"安全 = 思想+原理+体系"才对。经验派用"叠加单点工具"的思路,每加一个工具都让性能更差,但威胁覆盖率没升多少。方法派从威胁模型反推,每一项保护都精准对应一类攻击——保护强度 ↑、性能代价 ↓ 同时实现。
# 2.5 案例串联全文
- §03 安全思想 ▶▶ 成本对抗、纵深防御、最小权限——单点工具违反所有思想。
- §04 威胁模型 ▶▶ STRIDE 帮你看到"未识别的威胁"。
- §05 密码学 ▶▶ 自实现协议必崩,混合加密是数学必然。
- §08-§12 五道防线 ▶▶ 每道防线对应一类威胁,缺一不可。
- §14-§16 三层治理 ▶▶ 基线人人做、强化按风险、对抗精准用。
- §17 求证实验 ▶▶ 算法选择/加固对比/HTTPS 长连接 数据驱动。
# 03.安全思想原理
大部分团队对安全的认知停留在"用 HTTPS、加 AES"——这是工具层认知,不是思想层认知。本章讲清楚四个核心思想,后续所有具体技术都是这些思想的实现。
# 3.1 成本对抗思想
核心命题:安全的本质不是"绝对防御",而是"成本对抗"——让攻击成本 > 攻击收益。
攻击者收益(破解后能获得的利益)
────────────────────── > 1 ──▶ 会被攻击
攻击者成本(破解所需时间/资金/能力)
攻击者收益
────────────────────── < 1 ──▶ 不值得攻击(实质安全)
攻击者成本
2
3
4
5
6
7
关键事实:
- 任何安全措施都可被破解——只是时间问题。AES-256 用现代 GPU 暴力破解需要 10^54 年,但不是"不可破",是"破解成本 >> 宇宙寿命"。
- 设计目标不是"无法破解",是"破解不划算"。一个 5 元的优惠券保护,做到攻击者花 50 元才能破解就够了。
- 不同业务的"目标成本"不同:游戏外挂保护"破解成本 1 个月"够;银行 App 保护"破解成本 10 年"才及格。
工程含义:
- 过度防护无意义:给"5 元优惠券"加 VMP,破解成本 100 万,但攻击者根本不会来——这种保护是性能浪费。
- 保护强度要分级:对不同价值的资产,用不同强度的保护(详见 §16 三层治理)。
探索性思考:为什么"绝对安全"在数学上不存在? 因为攻击者拥有"无限重试"的能力——只要算法在运行,攻击者就能反复观察输入输出、反复尝试。理论上的"完美安全"只存在于一次性密码本(One-Time Pad)——但它要求密钥与明文等长且只用一次,工程上几乎不可用。所有实用密码学都是"计算上安全",不是"信息论上安全"。
# 3.2 纵深防御思想
核心命题:单层防御必被突破,多层防御才能延缓。
传统单层防御: [HTTPS] ── 攻击者绕过 ──▶ 全军覆没
│
▼
数据全暴露
纵深防御: [HTTPS] ── 突破 ──▶ [API 签名] ── 突破 ──▶ [服务端风控] ── 突破 ──▶ [审计追溯]
│
▼
及时发现+止损
2
3
4
5
6
7
8
9
为什么必须多层:
- 每一层都有可能被突破(HTTPS 可被中间人、签名可被逆向、服务端可被绕过)。
- 每层都有 90% 的拦截率,5 层叠加的总拦截率是 99.999%。
- 即使全部突破,审计层能让你"知道发生了什么"——这本身就是安全的一部分。
五道防线(详见 §08-§12):
| 防线 | 防什么 | 代表手段 |
|---|---|---|
| 1. 传输安全 | 网络嗅探/中间人 | HTTPS / mTLS / 证书 pinning |
| 2. 存储安全 | 本地数据窃取 | Keystore / Keychain / 加密 DB |
| 3. 代码保护 | 反编译/逆向 | 混淆 / 加固 / VMP |
| 4. 运行时防御 | 动态 hook/调试 | Root 检测 / 反调试 |
| 5. 完整性校验 | 包被篡改 | 签名校验 / App Attest |
探索性思考:为什么"链条最弱一环决定整体安全"? 因为攻击者不会从最强的地方下手——他们扫描整个攻击面,只攻最弱处。一个不加密的 API 让 99 个加密 API 失去意义(因为攻击者通过那个不加密的就能拿到全部数据)。这是为什么"做一半"和"完全不做"的安全风险几乎一样——做一半反而给团队"已经做了"的虚假安全感。
# 3.3 最小权限思想
核心命题:每个组件/用户/进程只拥有完成工作所必需的最小权限,不多一点。
经典反例:
- Android App 申请所有权限:通讯录、位置、相机、麦克风全要——这是"权限滥用",被监管和应用商店双重打击。
- 服务端用 root 跑业务:业务进程能访问整个文件系统——一个漏洞就让整机沦陷。
- 数据库用户都是 admin:业务代码能 DROP TABLE——一个 SQL 注入就毁掉所有数据。
最小权限的工程实践:
| 维度 | 最小权限化 |
|---|---|
| Android 权限 | 按需运行时申请,不在 manifest 一次性要全 |
| iOS 权限 | 同上,每个能力(位置/通知)独立申请 |
| 服务端进程 | 业务用专用低权限账户跑,不用 root |
| 数据库账户 | 业务账户只有读写指定表的权限,不能 DDL |
| 密钥访问 | 加密用的密钥与解密用的密钥分离(如签名公钥下发,私钥服务端保管) |
| API 设计 | 每个接口只接受必要参数,不接受"通用控制字段" |
最小权限思想与性能的关系:
- 权限越小,被利用面越小——攻击成本上升。
- 权限分离:不同模块用不同密钥/账户,一个泄露不影响其他。
- 审计更精确:每个操作的发起者明确,安全事件能精准溯源。
探索性思考:为什么"方便"是安全的最大敌人? 因为最小权限总是不方便——业务代码要细分权限、要为每个操作申请、要处理 "无权限"分支。开发者天然倾向于"开最大权限省事"。这是为什么权限收紧必须架构层强制(如 Android 6+ 强制运行时权限),靠开发者自觉是不可行的。
# 3.4 零信任思想
核心命题:永远不要信任任何输入、任何环境、任何客户端——即使是"你自己的"客户端。
传统思想:客户端是"可信的",服务端为客户端提供服务
零信任: 客户端是"敌对的",服务端必须验证一切
2
关键含义:
- 客户端是不可信的——所有客户端都可能被破解、被改包、被 hook。
- 客户端校验等于没校验——所有业务规则必须服务端校验。前端表单校验只是 UX 优化,不是安全。
- 客户端密钥必然泄露——不要把"只能客户端持有"的密钥放进客户端。
典型反例:
- 客户端校验金额是否合法 → 服务端不再校验:攻击者改包就能下 1 分钱订单。
- 客户端"标记"自己是 VIP → 服务端信任:攻击者直接传
is_vip=true。 - 客户端持有"主密钥"加密上传 → 服务端解密:客户端被破解 = 全部数据泄露。
零信任的具体落地:
- 服务端为权威:所有业务规则、价格计算、权限判定都在服务端。
- 客户端密钥派生:客户端持有的密钥从用户登录态派生(如 PBKDF2(用户密码) → 临时密钥),不是预置主密钥。
- API 签名:服务端验签确保请求未被篡改(详见 §14.3)。
- 行为风控:服务端基于行为模式识别异常(如同一账号 1 秒下 100 单)。
探索性思考:为什么"客户端加密"在安全里基本无意义? 因为客户端代码 = 公开代码(攻击者可以反编译)。客户端持有的密钥就是公开密钥。客户端加密能"防普通用户看包"(HTTPS 抓包看到密文),但防不了真正的攻击者——他们逆向出密钥后,所有"加密数据"都是明文。客户端加密 ≠ 端到端加密。前者只是"传输加密的延伸",后者是"用户密码派生密钥"——两者完全不同。
# 3.5 安全性能权衡
核心命题:所有安全措施都有性能代价,工程的目标不是"无限提升安全",而是"匹配业务风险等级"。
代价的具体表现:
加密 = CPU 时间 + 内存
加固 = 包体积 + 启动时长 + 可能的 JIT 失效
校验 = 启动时长 + IO
反调试 = 运行时检测开销
隔离 = IPC 开销
通信 = 握手延迟(首次)
2
3
4
5
6
权衡的三层模型:
┌──────────────────────────────────────┐
│ 必须做(关乎用户隐私 / 金钱) │
│ - HTTPS 全站 │
│ - 用户密码 hash + salt │
│ - 关键 API 签名 │
│ - 敏感数据本地加密存储 │
├──────────────────────────────────────┤
│ 视风险做(防破解 / 反作弊) │
│ - 代码混淆 │
│ - 加固 │
│ - 反调试 │
│ - 完整性校验 │
├──────────────────────────────────────┤
│ 一般不做(成本 > 收益) │
│ - WhiteBox 加密 │
│ - 完全自定义协议 │
│ - 极致防 hook │
└──────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
关键认知:
- 第一类是基线(不做就是事故)——详见 §14。
- 第二类按业务(金融/游戏 必做,工具 视情况)——详见 §15。
- 第三类大多数应用不必做——详见 §16(仅高对抗场景)。
# 04.威胁模型分析
不做威胁模型就开始写安全代码 = 不知道自己在防什么。本章用 STRIDE 框架让你系统化识别威胁。
# 4.1 STRIDE 六类威胁
STRIDE 是微软提出的威胁分类框架,覆盖应用安全的 6 类核心威胁:
| 字母 | 威胁 | 含义 | 典型场景 | 主要防御 |
|---|---|---|---|---|
| S | Spoofing 仿冒 | 攻击者伪装成合法用户/服务 | 钓鱼网站、伪造 token | 身份认证、HTTPS |
| T | Tampering 篡改 | 攻击者修改数据/代码 | 改包、改请求参数 | 完整性校验、签名 |
| R | Repudiation 抵赖 | 操作发生后无法追溯 | 用户否认下单 | 审计日志、不可否认签名 |
| I | Information Disclosure 信息泄露 | 攻击者获取敏感数据 | 抓包、本地数据被读 | 加密(传输+存储) |
| D | DoS 拒绝服务 | 让服务不可用 | 大量请求打挂服务 | 限流、风控 |
| E | Elevation of Privilege 权限提升 | 普通用户获得管理员权限 | SQL 注入、SSRF | 最小权限、输入校验 |
STRIDE 的工程价值:对每个组件、每个 API、每个数据流,逐一问这 6 个问题——能不能被仿冒/篡改/抵赖/泄露/拒服/提权?覆盖了这 6 类,威胁就基本完整识别。
# 4.2 攻击面盘点
应用的"攻击面"(Attack Surface)= 所有外部可触达的接口与数据:
| 攻击面 | 入口 | 攻击向量 |
|---|---|---|
| 网络层 | HTTP/HTTPS API | 中间人、抓包、改包、重放 |
| 本地存储 | 文件、数据库、SP、Keychain | 本地读、本地改 |
| 进程间通信 | Intent / URL Scheme / IPC | 恶意 App 调用 |
| 代码资源 | APK/IPA、JS Bundle | 反编译、改包 |
| 运行时 | 进程内存 | Hook、调试、内存抓取 |
| 物理层 | 设备本身 | 拆机、读 NAND |
减少攻击面的工程实践:
- 最少 API 暴露:不需要的 API 别开(如关闭 debug 接口)。
- 关闭多余的 IPC:不需要 export 的 Activity/Service 别 export。
- 删除调试代码:release 包不能含 dev 后端、不能含测试账号。
- 混淆敏感字符串:API URL、密钥常量都要混淆,不要明文出现在代码里。
# 4.3 攻击者画像
不同攻击者的能力、动机、手段差异巨大——威胁模型必须区分攻击者层级:
| 层级 | 能力 | 动机 | 典型手段 |
|---|---|---|---|
| L1 普通用户 | 不会编程,只会点 | 好奇 | 用 root 工具改个值 |
| L2 脚本小子 | 会用现成工具 | 薅羊毛、外挂 | Frida 现成脚本、抓包工具 |
| L3 业务黑产 | 团队作业 | 商业利益 | 自动化薅羊毛、规模刷单 |
| L4 专业逆向 | 专家级 | 商业克隆/IP 抢 | IDA、动态调试、自写 hook |
| L5 国家级 | 顶级 | 政治、间谍 | 0day、硬件攻击 |
关键工程决策:
- 大部分应用只需防到 L3——L4 以上(如国家级攻击)防不了,也不应该防(成本过高)。
- 金融/游戏需防到 L4——VMP/WhiteBox 才有意义。
- 没有应用真正能防 L5——只能依赖法律和外部威慑。
# 4.4 反直觉问题清单
带着这些问题阅读后续章节:
- AES 是不是越长 key 越安全越慢?
- RSA 真的能加密大数据吗?
- 加固一定让启动慢吗?
- 防 root / 防越狱真有用吗?
- WhiteBox 加密能完全防破解吗?
- 国密算法比 AES 慢多少?
- HTTPS 必须双向认证吗?
- 客户端加密是不是更安全?
# 05.密码学第一性
密码学是安全的"地基"——不理解原理就写安全代码 = 在沙地上盖楼。本章用最少的数学讲清楚四个核心机制:对称加密、非对称加密、哈希、签名。
# 5.1 对称加密原理
对称加密:加密密钥 = 解密密钥(双方共享同一个秘密)。
明文 + 密钥 K ──加密──▶ 密文
密文 + 密钥 K ──解密──▶ 明文
2
核心算法:
| 算法 | 类型 | 推荐用法 |
|---|---|---|
| AES-128/256 | 分组密码 | 首选,硬件加速 |
| ChaCha20 | 流密码 | 移动端无 AES 硬件时 |
| 3DES | 老分组密码 | 已弃用 |
| DES | 老分组密码 | 绝对弃用 |
| SM4 | 国密分组 | 合规需求 |
关键设计:必须用 AEAD 模式(Authenticated Encryption with Associated Data),如 AES-GCM / ChaCha20-Poly1305——同时加密 + 完整性校验。
不要用 AES-ECB:相同明文产生相同密文,无完整性校验
不要用 AES-CBC(单独):可被填充预言攻击
要用 AES-GCM:加密 + tag(防篡改),工业标准
2
3
探索性思考:为什么 AES-128 已经"够安全"? 因为破解 AES-128 需要尝试 2^128 ≈ 3×10^38 个密钥。即使用全球所有计算机不停算 100 年,也只能测试 2^96 个。AES-128 在可见的物理时间内不可破解。AES-256 主要意义是"抗量子计算"——量子计算机用 Grover 算法可以把破解复杂度降低一半(128 → 64 位),AES-256 降到 128 位仍安全,AES-128 降到 64 位就危险。对量子无忧的应用,AES-128 就够,性能更好。
# 5.2 非对称加密原理
非对称加密:加密密钥(公钥)≠ 解密密钥(私钥),数学上保证"知道公钥推不出私钥"。
明文 + 公钥 PK ──加密──▶ 密文
密文 + 私钥 SK ──解密──▶ 明文
公钥可以公开,私钥严守秘密
2
3
4
核心算法:
| 算法 | 安全基础 | 1024/2048/4096 安全等级 | 性能 |
|---|---|---|---|
| RSA | 大整数分解难题 | RSA-2048 ≈ 80-112 bit | 慢(毫秒级) |
| ECC(ECDH/ECDSA) | 椭圆曲线离散对数 | P-256 ≈ 128 bit | 快(亚毫秒) |
| Ed25519 | 改进椭圆曲线 | ≈ 128 bit | 最快 |
| SM2 | 国密椭圆曲线 | ≈ 128 bit | 快 |
关键认知:
- 非对称加密慢 100-1000 倍于对称加密——不能用来加密大数据。
- 非对称加密的根本作用是"密钥交换"——双方用非对称协商出一个对称密钥,然后用对称密钥加密数据。
- ECC 在同等安全等级下密钥更短、运算更快——现代设计首选 ECC,RSA 仅在兼容性需求时用。
RSA 不能加密大数据的原因:RSA 的明文长度必须 < 模数长度(如 RSA-2048 一次最多加密 245 字节,扣除 padding 后实际更少)。要加密更大数据需要分块——但分块 RSA 的安全性下降且性能极差。
# 5.3 哈希与签名
哈希函数:把任意长度数据映射成固定长度"指纹",不可逆。
任意数据 ──hash()──▶ 固定长度摘要(如 SHA-256: 32 字节)
性质:
1. 单向性:从摘要无法反推原文
2. 抗碰撞:找两个不同输入产生同摘要在数学上不可行
3. 雪崩效应:输入改 1 bit,输出全变
2
3
4
5
6
核心算法:
| 算法 | 输出长度 | 状态 |
|---|---|---|
| MD5 | 128 bit | 已破解,弃用 |
| SHA-1 | 160 bit | 已弱,弃用 |
| SHA-256 | 256 bit | 首选 |
| SHA-512 | 512 bit | 高强度场景 |
| BLAKE2/3 | 可变 | 新一代,更快 |
| SM3 | 256 bit | 国密首选 |
密码哈希的特殊要求——存用户密码时绝不能直接用 SHA-256:
错误:sha256(password)
问题:彩虹表攻击——预先算好常见密码的 hash,对照查询
正确:argon2/bcrypt/scrypt(password, salt)
特性:
- salt:每用户随机,让彩虹表无效
- 慢函数:故意让 hash 计算慢(10ms-1s),让暴力破解变慢
2
3
4
5
6
7
数字签名:用私钥对消息生成签名,任何人用公钥能验证——证明消息来自私钥持有者:
消息 + 私钥 SK ──签名──▶ signature
消息 + signature + 公钥 PK ──验证──▶ 真伪
性质:
1. 不可伪造(没私钥签不出)
2. 不可否认(只有私钥持有者能签)
3. 完整性(消息改了签名失效)
2
3
4
5
6
7
代表算法:RSA 签名 / ECDSA / Ed25519。
探索性思考:为什么"签名"和"加密"是不同的? 直觉上"用私钥加密 = 签名",但这是错的:
- 加密目的是保密:用对方的公钥加密,只有对方私钥能解。
- 签名目的是认证:用自己的私钥签,所有人都能用公钥验证。
RSA 在数学上恰好可以互换(私钥能"加密",公钥能"解密"),但ECC 加密和 ECC 签名是完全不同的算法。在工程上永远要用专门的"sign/verify" API,不要用 "encrypt/decrypt" 模拟签名。
# 5.4 混合加密的必然
为什么所有现代密码协议(TLS / PGP / 微信加密协议)都是"对称 + 非对称"混合?
加密大数据:
非对称加密慢 1000×、限制大小
对称加密快、无大小限制
↓
只用对称:怎么把密钥安全地告诉对方?
只用非对称:太慢
↓
混合方案:
1. 用非对称交换/协商一个对称密钥(少量数据,慢一点没事)
2. 用对称密钥加密实际数据(大量数据,必须快)
2
3
4
5
6
7
8
9
10
TLS 1.3 的简化流程:
1. 客户端 → 服务端:发起握手 + 客户端随机数
2. 服务端 → 客户端:服务端证书(含公钥)+ 服务端随机数
3. 客户端用 ECDH 与服务端协商出共享密钥
4. 后续通信用 ECDH 协商出的对称密钥(AES-GCM)加密
2
3
4
这就是为什么 §17.1 实验 中"全 RSA"方案不可用、"RSA + AES" 方案是工业标准。
# 5.5 密钥管理本质
密码学的最大难题不是算法选择,是密钥管理——再好的算法,密钥泄露就全完蛋。
密钥的全生命周期:
生成 ──▶ 存储 ──▶ 使用 ──▶ 轮换 ──▶ 销毁
每个阶段都有泄露风险
2
3
4
关键原则:
- 密钥不入代码仓:硬编码密钥反编译就拿到(详见 §14.4)。
- 密钥不入日志:日志里打印密钥 = 把密钥送给所有 SRE。
- 密钥不入备份:明文备份等于多个泄露点。
- 密钥用专门设备保管:客户端用系统 Keystore/Keychain(详见 §9.3),服务端用 HSM/KMS。
- 密钥定期轮换:长期使用同密钥风险累积,应支持平滑轮换。
- 密钥分级:根密钥保护工作密钥、工作密钥保护数据密钥(密钥分层)。
探索性思考:为什么"最好的密钥保护是不保护"? 这不是悖论——意思是让客户端根本不持有长期密钥。例如:
- 客户端只保留登录态 token(短期、可吊销)。
- 真正的加密密钥从用户密码派生(PBKDF2/Argon2),不存任何地方。
- 支付密钥服务端临时下发,用完即销毁。
"没存的密钥不会泄露"——这是密钥管理的至高境界。
# 06.度量与采集
# 6.1 三类采集方案
① 性能开销监控(加密/校验耗时)
② 安全告警(异常行为/风险事件)
③ 攻击模拟(线下渗透测试)
2
3
① 性能开销监控——在加密/校验等关键操作处埋点,监控各操作的实际耗时与失败率。物理本质:用真实数据对照"理论性能",发现异常退化(如某机型 AES 突然慢 10×)。适用边界:所有应用必备的常驻监控。
② 安全告警——监控运行时风险事件(root 检测、hook 检测、签名校验失败、异常解密失败)。物理本质:把"客户端环境异常"上报到服务端,触发风控决策。适用边界:金融/游戏/高安全应用必备。
③ 攻击模拟——线下用攻击者视角扫描应用漏洞(反编译、Frida 试探、HTTPS 抓包、改包测试)。物理本质:以白盒/灰盒方式发现"防御者视角看不到的问题"。适用边界:发版前必跑的安全 audit。
三类方案的总览
| 方案 | 钩子位置 | 数据粒度 | 性能开销 | 跨端通用性 | 主要局限 |
|---|---|---|---|---|---|
| ① 性能监控 | 代码内 | 操作级 | 极低 | 高 | 不知风险 |
| ② 安全告警 | 运行时 | 事件级 | 低 | 高 | 误报/漏报 |
| ③ 攻击模拟 | 线下 | 黑盒 | 无(线下) | 高 | 一次性 |
# 6.2 各方案盲区
| 现象 | 方案 ① | 方案 ② | 方案 ③ |
|---|---|---|---|
| 加密太慢 | ✅ | ❌ | ❌ |
| 被动态 hook | ❌ | 部分 | ✅ |
| 被反编译破解 | ❌ | ❌ | ✅ |
| 异常解密失败 | 部分 | ✅ | ❌ |
| 业务伪造(薅羊毛) | ❌ | 部分 | ✅ |
组合定律:① + ② 必须组合(覆盖线上)+ ③ 补全开发期。
# 6.3 跨平台采集
| 维度 | Android | iOS | Web |
|---|---|---|---|
| 加密耗时 | Cipher.doFinal 计时 | CryptoKit signpost | crypto.subtle 计时 |
| Root 检测 | RootBeer / 自实现 | 越狱检测库 | N/A(沙箱足够) |
| Hook 检测 | Frida 检测 / Xposed 特征 | 同 | N/A |
| 签名校验 | PackageInfo + cert 比对 | 系统级 | SRI |
| 抓包检测 | 检测代理/VPN | 同 | N/A |
# 6.4 数据可信度
| 数据 | 可信度 | 偏差来源 |
|---|---|---|
| 加密耗时 | 高 | 直接计时 |
| Root/越狱检测 | 中 | 检测可被绕过 |
| Hook 检测 | 中 | 攻击者升级对抗 |
| 攻击模拟 | 高(线下) | 仅一次性 |
# 07.归因决策树
# 7.1 安全性能决策树
安全相关性能问题
│
├── 加密太慢 ──▶ 算法选择(§5)
│ ├─ 大数据用对称加密(AES)
│ ├─ 小数据/密钥协商用非对称(ECC > RSA)
│ ├─ 哈希用 SHA-256
│ └─ 国密视场景
│
├── 加固让启动慢 ──▶ 加固方案(§10.3)
│ ├─ DEX 加密 → 启动时解密
│ ├─ Native 加固 → 启动时初始化
│ └─ 评估必要性(不是所有代码都需要)
│
├── HTTPS 慢 ──▶ 传输优化(§8)
│ ├─ 长连接(卷四·02)
│ ├─ TLS 1.3(1-RTT)
│ └─ 0-RTT 重连
│
└── 防 hook 影响调试 ──▶ Debug 配置
├─ release 启用/debug 关闭
└─ 灰度环境配置
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 7.2 加密代价归因
算法选择决定一半性能:
| 场景 | 推荐 | 不推荐 |
|---|---|---|
| 大数据本地加密 | AES-256-GCM | RSA(不能加密大数据) |
| 密钥协商 | ECDH | RSA-2048(慢) |
| 数据签名 | ECDSA / Ed25519 | RSA-4096 |
| 哈希 | SHA-256 / Blake2 | MD5(不安全)/ SHA-1(已弱) |
| 密码哈希 | Argon2 / bcrypt | SHA-256(不防彩虹表) |
典型耗时(Pixel 6):
| 算法 | 1KB 耗时 | 1MB 耗时 |
|---|---|---|
| AES-256-GCM | 50μs | 8ms |
| AES-128 | 30μs | 5ms |
| RSA-2048 加密 | 500μs | 不适用 |
| RSA-2048 签名 | 5ms | 5ms(恒定) |
| ECDSA P-256 | 1ms | 1ms |
| SHA-256 | 5μs | 4ms |
# 7.3 加固代价归因
加固的性能代价:
- 包体积 +5-15MB:加固 SDK + 加密 DEX。
- 启动 +100-500ms:解密 DEX/初始化加固运行时。
- JIT 失效:加固代码 JIT 编译可能失效,运行时性能下降。
归因方法:
- 启动 trace 中识别加固 SDK 耗时。
- 对比加固前后冷启动时长。
- 分析加固范围——整包加固 vs 关键模块加固性能差几倍(详见 §15.1)。
# 08.传输安全全链路
传输安全是第一道也是最容易做对的防线——HTTPS 已经是现代应用的"地板"。回答:TLS 到底防的是什么 / 证书校验为何是 HTTPS 的核心 / 为什么单向 HTTPS 不够。
# 8.1 传输威胁的本质
网络传输面临的威胁本质上只有 4 类:
| 威胁 | 攻击者能力 | 防御目标 |
|---|---|---|
| 窃听 | 读取传输内容 | 保密性(加密) |
| 篡改 | 修改传输内容 | 完整性(MAC/签名) |
| 重放 | 重发历史请求 | 时效性(时间戳/nonce) |
| 冒充 | 伪装成对端 | 真实性(证书/签名) |
HTTPS 同时解决前三个,证书系统解决第四个。但重放攻击是 HTTPS 不防的——这是为什么 API 还要单独签名(详见 §14.3)。
# 8.2 TLS 握手原理
TLS 1.3 的核心简化流程:
客户端 服务端
│ ClientHello(支持算法+随机数) │
│ ────────────────────────────────▶ │
│ │
│ ServerHello(选定算法+随机数) │
│ + 证书 + 公钥参数 │
│ ◀──────────────────────────────── │
│ │
│ [验证证书是否可信] │
│ [基于公钥参数 + 自己的私钥] │
│ [用 ECDH 算出共享密钥] │
│ │
│ Finished(用共享密钥加密) │
│ ────────────────────────────────▶ │
│ │
│ 应用数据(AES-GCM 加密)│
│ ◀────────────────────────────────▶│
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关键事实:
- TLS 1.3 = 1-RTT 握手(比 TLS 1.2 少一轮交互)。
- 会话复用 = 0-RTT(客户端首次握手后保留 session ticket,下次直接用)。
- 加密算法是握手时协商的——双方支持的算法集合的交集(如 AES-GCM 或 ChaCha20-Poly1305)。
性能含义:HTTPS 的"慢"主要在首次握手(多一轮 RTT),长连接复用后几乎零开销——这就是 §17.3 实验 的数据来源。
# 8.3 证书校验机制
证书是 TLS 的"身份证"——证明"持有这个公钥的就是 example.com 而不是别人"。
证书链信任模型:
根 CA 证书(操作系统/浏览器内置)
│
▼ 签发
中间 CA 证书
│
▼ 签发
example.com 服务端证书(含公钥)
校验时倒过来走:
1. 服务端发来 example.com 的证书
2. 客户端验证:这个证书是中间 CA 签的吗?
3. 中间 CA 是根 CA 签的吗?
4. 根 CA 在我的信任列表里吗?
2
3
4
5
6
7
8
9
10
11
12
13
默认 HTTPS 校验的内容:
- 证书签名是否合法(用 CA 公钥验证)。
- 证书是否过期。
- 证书域名是否匹配请求的 host。
- 证书是否被吊销(OCSP/CRL)。
关键工程点:
- 不要禁用证书校验——虽然测试时方便(自签证书),但代码里残留
trustAllCertificates是经典灾难。 - 证书 pinning:把"信任的证书指纹"硬编码进客户端,不再信任系统 CA——防止 CA 被攻破/被植入恶意 CA。代价:服务端换证书时客户端必须同步更新。
# 8.4 双向认证 mTLS
普通 HTTPS:服务端证书 → 客户端验证(单向)。
双向认证 mTLS:服务端证书 → 客户端验证 + 客户端证书 → 服务端验证。
单向 HTTPS:
客户端 ──证书校验──▶ 服务端身份可信
服务端 ←─请求──── 客户端身份不可信(HTTPS 层不知道是谁)
双向 mTLS:
客户端 ──证书校验──▶ 服务端身份可信
服务端 ──证书校验──▶ 客户端身份可信
2
3
4
5
6
7
适用场景:
- B2B API:合作方调用,必须双向认证。
- 核心金融 API:登录态之上还要"设备身份"。
- 企业内网:员工设备访问内部系统。
不适用场景:
- 普通 C 端应用——客户端证书无法安全保管(第 3.4 节零信任:客户端可被破解,证书必然泄露)。
# 8.5 中间人攻击防御
中间人(MITM)攻击:攻击者插入到客户端和服务端之间,对双方都伪装成"对端":
正常:
客户端 ────HTTPS───▶ 服务端
中间人:
客户端 ────HTTPS───▶ [攻击者代理] ────HTTPS───▶ 服务端
│
▼
解密、读取、改写、再加密
2
3
4
5
6
7
8
实现方法:攻击者让用户手机信任攻击者的"假根 CA"(通过钓鱼/恶意 App 安装),之后攻击者签发任意域名的"假证书"客户端都会信任。
防御层级:
| 防御 | 抵抗能力 | 工程代价 |
|---|---|---|
| HTTPS(默认 CA 信任) | 防普通嗅探,防不了 MITM | 0 |
| HSTS | 防 HTTPS 降级(强制 HTTPS) | 0 |
| 证书 pinning | 防 CA 攻破型 MITM | 服务端换证书需协同 |
| 公钥 pinning | 同上,但更灵活 | 同上 |
| mTLS | 防"客户端被冒充" | 客户端证书管理复杂 |
探索性思考:为什么"证书 pinning 是把双刃剑"? 它的好处是断绝 CA 信任链的所有风险——即使根 CA 被攻破,应用也不受影响。 它的坏处是与服务端紧耦合——服务端换证书必须客户端同时更新(否则用户白屏)。
真实事故:某金融 App 服务端证书过期前忘了通知客户端团队,证书 pinning 配置没更新——全国用户登录失败 3 小时。所以 pinning 必须配合"证书过期监控+自动告警"工程化运维。
# 09.存储安全全链路
存储安全防的是"本地数据被读"——这类威胁在 Android 比 iOS 更常见(因为 Android 沙箱更宽松、用户更容易 root)。回答:沙箱到底防什么 / 系统密钥库的硬件保护原理 / 为什么"客户端加密 ≠ 安全"。
# 9.1 存储威胁的本质
本地数据面临 4 类威胁:
| 威胁 | 攻击者能力 | 典型路径 |
|---|---|---|
| 同设备恶意 App | 读其他 App 的数据 | 利用沙箱漏洞、共享存储 |
| 物理拆机 | 读 NAND 闪存 | 取证/间谍 |
| 设备 root/越狱 | 拥有 root 权限 | 用户自愿安装的工具 |
| 备份泄露 | 读用户的云备份 | iCloud 被盗 |
对应防御:
- 同设备:依靠沙箱+文件权限(详见 §9.2)。
- 拆机:依靠硬件密钥库(详见 §9.3)。
- Root/越狱:理论上不可防——只能让攻击成本上升(详见 §11.2 风控降级思路)。
- 备份泄露:敏感数据加密 + 标记不参与备份。
# 9.2 沙箱与文件权限
Android 沙箱模型:
每个应用有独立的 Linux UID(用户 ID)
↓
应用的私有目录 /data/data/<pkgName>/ 用文件系统权限保护(仅 UID 可读)
↓
其他应用(不同 UID)即使知道路径也读不到
2
3
4
5
iOS 沙箱模型:
每个应用有独立的 sandbox container
↓
应用只能访问自己 container 内的目录
↓
跨 App 数据交换必须通过系统 API(UIDocumentPicker/AppGroup)
2
3
4
5
沙箱能防什么:
- ✅ 普通同设备 App 互读
- ✅ 普通 App 越界访问系统目录
沙箱不能防什么:
- ❌ Root/越狱设备(攻击者已获得 OS 级权限)
- ❌ 物理拆机(直接读硬件存储)
- ❌ 应用自己被反编译后拿到密钥
Android 的特殊性:
- 部分老版本 Android 允许把数据放到 SD 卡(
getExternalStorage)——SD 卡是所有 App 共享的,敏感数据放这里就是裸奔。 - Android 11+ Scoped Storage 强化了沙箱,外部存储也按应用隔离。
# 9.3 系统密钥库原理
核心思想:让密钥永远不出现在应用进程的内存里——加密/解密都委托给系统服务,应用只拿到结果。
传统做法:
应用代码 → 把密钥读到内存 → 调 AES.encrypt(key, data)
↑
密钥曾出现在进程内存里 → Hook/dump 都能拿
密钥库做法:
应用代码 → 调 SystemKeystore.encrypt(keyAlias, data)
↓
系统服务在独立进程/TEE 里用密钥加密
↓
返回密文给应用
↑
密钥从未出现在应用进程的内存里
2
3
4
5
6
7
8
9
10
11
12
13
Android Keystore:
| 安全级别 | 实现 | 攻击成本 |
|---|---|---|
| Software-only | 系统进程中用一个主密钥加密保管 | 中(root 后可能拿到) |
| TEE(Trusted Execution Environment) | ARM TrustZone 隔离的安全执行环境 | 高(需绕过 TEE) |
| StrongBox | 独立的安全芯片(Pixel 3+/部分旗舰) | 极高(需物理破解芯片) |
iOS Keychain:
- 默认走 Secure Enclave(A7+ 芯片自带的独立安全协处理器)。
- 密钥从生成到使用全部在 Secure Enclave 内——应用代码完全无法读取密钥本身,只能"调用密钥做运算"。
- iOS 因此天然比 Android 安全——不需要"加固密钥保护"。
工程使用:
// Android:用 EncryptedSharedPreferences(背后是 Keystore)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sp = EncryptedSharedPreferences.create(...)
sp.edit().putString("token", "secret").apply()
2
3
4
5
6
// iOS:用 Keychain
let query: [String: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: "user",
kSecValueData: "secret".data(using: .utf8)!,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
]
SecItemAdd(query, nil)
2
3
4
5
6
7
8
探索性思考:为什么 iOS 的 Secure Enclave 是"硬件级安全的天花板"? Secure Enclave 是 物理上独立的处理器(不是软件隔离)——它有自己的 RAM、自己的固件、自己的密钥。主 CPU 完全无法直接读它的内存,只能通过严格的 API 协议跟它通信。即使主 OS 完全沦陷(越狱),Secure Enclave 里的密钥仍然安全。这是为什么"iOS Touch ID/Face ID 数据不会上传"——它从来没出过 Secure Enclave。
# 9.4 数据加密落地
完整的本地数据加密流程:
1. 应用启动时,从系统密钥库取 masterKey(永不离开密钥库)
2. 用 masterKey 加密保护 dataKey(轻量对称密钥)
3. 用 dataKey 加密用户数据
4. 重启应用:重新取 masterKey → 解密 dataKey → 解密数据
2
3
4
为什么要"两层密钥"?
- masterKey 性能慢(在密钥库里运算有 IPC 开销,毫秒级)。
- dataKey 性能快(直接 AES-GCM,微秒级)。
- 用 masterKey 保护 dataKey 让"安全性 + 性能"双赢。
敏感数据分级:
| 敏感等级 | 数据 | 推荐方案 |
|---|---|---|
| 极敏感 | 支付 PIN、生物特征模板 | Keychain/Keystore,从不读出 |
| 高敏感 | 用户密码、token、私钥 | Keychain/Keystore 直接保存 |
| 中敏感 | 用户身份证、手机号、地址 | EncryptedSP/加密 DB(dataKey) |
| 低敏感 | 设置项、UI 状态 | 普通 SharedPreferences/UserDefaults |
# 10.代码保护全链路
代码保护是为了抬高反编译/逆向成本——但要清楚:所有客户端代码理论上都可被逆向。回答:混淆和加固的本质区别 / VMP 为何是当前最强方案。
# 10.1 反编译威胁本质
开发者代码 ──编译──▶ 二进制(DEX/Mach-O/JS)
│
│ 反编译/反汇编
▼
攻击者拿到代码(语义化或汇编)
│
├─ 抄业务逻辑(IP 损失)
├─ 找漏洞(绕过校验)
├─ 提取常量(API key/算法常数)
└─ 改包重发(破解版/外挂)
2
3
4
5
6
7
8
9
10
各平台反编译难度:
| 平台 | 反编译产物 | 难度 |
|---|---|---|
| Android(Java/Kotlin) | DEX → smali → 接近 Java 源码 | 极低 |
| Android(Native C++) | .so → 汇编 → IDA 可还原 | 中 |
| iOS(OC/Swift) | Mach-O → 汇编(含 OC class 元信息) | 中 |
| Web(JS) | JS bundle 几乎是源码 | 极极低 |
| WebAssembly | wasm → 汇编 | 中 |
关键认知:Java/Kotlin 是反编译最容易的平台——这是 Android 加固生态比 iOS 更成熟的根本原因。
# 10.2 代码混淆原理
混淆的本质:把"语义化的名字"换成"无意义的短名",不改变运行行为。
原始:
class UserManager {
fun login(username: String, password: String): User { ... }
}
混淆后:
class a {
fun a(b: String, c: String): Object { ... }
}
2
3
4
5
6
7
8
9
混淆能解决什么:
- ✅ 让攻击者难"读懂代码意图"(看到
a.b()不知道是登录还是支付)。 - ✅ 减小 APK 体积(短名比长名小)。
- ✅ 提高静态分析成本。
混淆不能解决什么:
- ❌ 防动态调试——运行时仍能看到字节码逻辑。
- ❌ 防字符串提取——明文字符串(API URL/密钥)混淆不掉。
- ❌ 防关键算法被理解——逻辑结构没变,资深逆向仍能读懂。
Android R8/ProGuard 工程实践:
关键配置:
- 启用 R8(默认开启)
- 不要 -keep 太多(每个 keep 都是混淆漏洞)
- 字符串单独混淆(ProGuard StringObfuscator 插件)
- enableR8FullMode = true(更激进的优化)
2
3
4
5
探索性思考:为什么 R8 比 ProGuard 更安全? R8 在 Google 接管后做了三件事: ① 更激进的内联:把小方法 inline 到调用点——攻击者看到的是"一坨平展代码",而不是"清晰的方法层次"。 ② 更彻底的死代码剔除:未使用的代码(包括 debug 代码、testFlag 分支)被删除——减少攻击面。 ③ 更智能的常量折叠:编译期能算的都算了——攻击者反编译看到的是结果,不是计算过程。 这三点让"理解代码意图"显著变难。
# 10.3 加固方案对比
加固比混淆更进一步——不只是改名字,是改运行机制:
| 方案 | 原理 | 防护强度 | 性能代价 |
|---|---|---|---|
| DEX 加密 | DEX 文件加密保存,运行时解密加载 | 中 | 启动 +100-200ms |
| DEX 抽取 | 关键方法体抽出来加密,运行时填回 | 中-高 | 启动 +50-100ms |
| Native 加固 | 关键代码移到 .so,运行时加密执行 | 高 | 启动 +50-200ms |
| VMP(虚拟机保护) | 关键代码翻译成自定义指令集,自带解释器 | 极高 | 启动 +200-500ms + 运行时慢 10-100× |
§17.2 实验 用真实数据对比了三家加固服务——包体积可差 3 倍、启动时长可差 4 倍。
选型核心问题:
- 业务真的需要这么强的保护吗? 工具类应用根本不需要加固。
- 关键代码占比多少? 全包加固代价大,只对核心模块加固性能影响小很多(详见 §16.1)。
# 10.4 VMP 虚拟机保护
VMP(Virtual Machine Protection)是当前最强的代码保护方案:
原始代码:
ADD R1, R2 ← 标准 ARM 指令
MOV R3, R1
...
│
│ VMP 编译
▼
自定义字节码:
0x4F 0x2A 0x88 ... ← 攻击者不认识的指令
│
│ 运行时
▼
VMP 解释器(embedded in app)逐条解释执行
2
3
4
5
6
7
8
9
10
11
12
13
为什么 VMP 极难破解:
- 攻击者必须先逆向出 VMP 的指令集架构(每个 vendor 都不同)。
- 然后逆向出 VMP 解释器才能"翻译回标准指令"。
- 这个工作量是月级别——而且每次 VMP 升级都要重做。
VMP 的代价:
- 运行时慢 10-100×:每条原始指令变成"读字节码 → 解释 → 执行"多步。
- 包体积 +5-15MB:VMP 解释器本身就大。
- 不能整包用:性能扛不住,只对关键函数用(订单计费、风控规则、密钥派生)。
探索性思考:为什么 VMP "值得"用却"不能滥用"? 因为它的**"保护强度 / 性能代价"曲线是非线性的**——
- 用 VMP 保护 1% 的代码(核心算法):保护强度极高,性能影响 1%。
- 用 VMP 保护 10% 的代码:保护强度无显著提升(攻击者从未保护的 90% 找漏洞),但性能影响变 10%。
- 用 VMP 保护 100% 的代码:性能完全崩溃,且仍然"链条最弱一环"。
VMP 的工程艺术是"挑选最关键的 1%"。详见 §16.1。
# 11.运行时防御全链路
静态保护(混淆/加固)防的是"反编译看代码",运行时防御防的是"动态 hook 看运行"。回答:Frida 类工具的工作原理 / 为什么 Root 检测不能简单"发现就退出"。
# 11.1 动态攻击的本质
Frida / Xposed / Cydia Substrate 这类动态 hook 框架的能力:
正常调用:
App ──▶ MethodA() ──▶ 返回值
Hook 后:
App ──▶ [Hook 拦截] ──▶ 攻击者代码 ──▶ 修改参数/返回值 ──▶ MethodA()
│
▼
或不调原方法
2
3
4
5
6
7
8
典型攻击场景:
- 绕过登录校验:hook
isLoggedIn()始终返回 true。 - 修改业务参数:hook 网络请求层,把
price=1000改成price=1。 - 抓取密钥:hook AES.encrypt 的入参,得到明文 + 密钥。
- 篡改逻辑:hook 风控判定函数,强制返回"无风险"。
hook 的实现原理:
- Frida:把 Frida runtime 注入应用进程,运行时改方法 entry 跳转到 Frida 的 trampoline。
- Xposed:替换 Zygote 进程,所有 fork 出的应用都被 hook。
- 运行时的本质都是修改"已加载的代码内存"——必须先获得 root/越狱权限。
# 11.2 Root 与越狱检测
Root/越狱本身不是攻击——是攻击的"前置条件"。检测的目的是识别风险用户,不是阻断他们。
检测方法:
| 平台 | 检测信号 |
|---|---|
| Android Root | /system/xbin/su 文件存在 / getprop ro.debuggable=1 / busybox 存在 |
| Android 模拟器 | 特定厂商字符串(goldfish、ranchu)/ CPU 是 x86 |
| iOS 越狱 | /Applications/Cydia.app 存在 / 沙箱外可写 / dyld 含越狱注入库 |
关键工程决策:检测到 Root/越狱后,怎么办?
| 策略 | 优点 | 缺点 |
|---|---|---|
| A 直接退出 | 最严格 | 国产 ROM 误判率高(10%+),用户流失 |
| B 降级体验 | 兼顾业务 | 需业务侧支持降级 |
| C 仅风控记录 | 不影响用户 | 服务端风控压力大 |
| D 动态决策 | 最优 | 实现复杂 |
主流推荐 C+D 组合:
// 客户端只检测 + 上报,不阻断
val isRooted = checkRoot()
api.reportDeviceRisk(isRooted = isRooted)
// 服务端按业务决策:
// - 普通用户:正常服务
// - Root 用户:限制大额支付、限制提现、加强人机验证
2
3
4
5
6
7
探索性思考:为什么"发现 Root 就退出"是经典反模式? 三个原因: ① 国产 ROM 标记自己 root(很多用户根本没真 root,是厂商偷懒)。 ② 真正的攻击者会绕过检测(修改
getprop、删除 su 文件痕迹)。 ③ 正常用户被误伤——一个朋友用 EMUI 突然进不去,给应用差评。结果:检测拦不住坏人,却伤了好人。正确思路是**"识别 ≠ 阻断"**——把"是否 root"当作"风险评分"的一个因子,由服务端综合判定。
# 11.3 Hook 检测原理
检测 Frida 类工具的常见方法:
| 检测点 | 原理 |
|---|---|
| 进程列表 | 检查是否有 frida-server 进程在跑 |
| 加载的 .so | 检查 /proc/self/maps 是否含 frida-agent.so |
| 特征端口 | Frida 默认监听 27042 端口 |
| 方法 entry 校验 | 关键方法的前几字节是否被替换成跳转指令 |
| 系统调用观察 | 异常的 ptrace/mprotect 调用 |
对抗的本质:检测和反检测是猫鼠游戏——攻击者总能升级绕过。
应用:检测 frida-agent.so → 攻击者:改 so 名字
应用:检测特征端口 → 攻击者:改默认端口
应用:检测进程名 → 攻击者:rename 进程
应用:检测 inline hook 痕迹 → 攻击者:用更隐蔽的 hook 方式(PLT/got 改写)
2
3
4
工程现实:检测只能拦住 80% 的脚本小子,对专业攻击者基本无效。真正有效的是"服务端风控 + 业务规则"——攻击者哪怕 hook 成功,服务端也能识别"异常行为模式"。
# 11.4 调试与抓包检测
反调试:
- Linux ptrace 自检:调用
ptrace(PTRACE_TRACEME),调试器在用就会失败。 - TracerPid 检查:
/proc/self/status中 TracerPid 不为 0 = 被调试。 - iOS sysctl 检查:通过
sysctl(kinfo_proc)检测 P_TRACED flag。
抓包检测:
- 检查系统代理设置(HTTP_PROXY 环境变量、
Settings.Global.HTTP_PROXY)。 - 检查 VPN 状态(部分抓包工具用 VPN 实现)。
- 证书 pinning(直接断绝 Charles 类抓包工具,因为它们用自签证书)。
调试器误伤问题:
- release 包启用反调试:开发者在线上版本调试时被阻断(罕见但有用)。
- debug 包关闭反调试:开发期不影响调试。
- CI 灰度版本可选开:内部测试时也开启检查防御逻辑。
# 12.完整性校验全链路
完整性校验防的是"包被改后重发"——攻击者改了 APK 重签名分发,或改了 JS 资源放 CDN。回答:APK 签名是怎么回事 / App Attest 凭什么"证明设备完整性"。
# 12.1 篡改威胁本质
开发者签的原版 APK
↓ 攻击者下载
↓ 反编译、修改业务代码(如去广告、加外挂)
↓ 用攻击者自己的证书重新签名
↓ 分发到第三方应用市场
↓ 用户安装"破解版"
2
3
4
5
6
完整性校验的目标:让"被篡改的版本无法在你的服务端正常工作"。
注意:单纯客户端校验"自己的签名"基本无效——攻击者会同时把校验代码改成永远返回 true。必须服务端介入才有效。
# 12.2 包签名校验
Android APK 签名机制:
开发者签名:
APK 内容 ──hash──▶ 摘要
摘要 + 开发者私钥 ──签名──▶ APK 签名(写入 META-INF)
验证(Android 系统 + 应用自己):
APK 内容 ──hash──▶ 摘要1
APK 签名 + 开发者公钥 ──验证──▶ 摘要2
摘要1 == 摘要2 → APK 未被改
2
3
4
5
6
7
8
应用层签名校验的工程模式:
fun verifySignature(): Boolean {
val pkgInfo = packageManager.getPackageInfo(packageName, GET_SIGNING_CERTIFICATES)
val signatures = pkgInfo.signingInfo.apkContentsSigners
val sha256 = sha256(signatures[0].toByteArray())
return sha256 == EXPECTED_SHA256 // 硬编码的开发者证书哈希
}
2
3
4
5
6
关键问题:EXPECTED_SHA256 写在代码里,攻击者反编译后改成自己的证书哈希——校验就被绕过。
正确做法:结合服务端——把签名哈希上报服务端,服务端校验:
客户端:sha256 = 计算自己包的签名 → 上报给服务端(带在每个 API 头里)
服务端:
if sha256 != expected: 标记设备风险,返回 403 或降级数据
2
3
# 12.3 设备完整性证明
Android Play Integrity API(取代 SafetyNet):
应用 ──请求挑战──▶ Google Play 服务
Play 服务 ──验证设备 + 应用包名 + 签名──▶ 生成 token(Google 私钥签名)
应用 ──token──▶ 自己服务端
自己服务端 ──用 Google 公钥验证 token──▶ 确认设备/应用真实
2
3
4
iOS App Attest(iOS 14+):
类似机制:
应用 ──请求──▶ Apple 服务器(生成挑战)
设备 Secure Enclave ──签名挑战──▶ token
应用 ──token──▶ 自己服务端
自己服务端 ──用 Apple 公钥验证──▶ 确认设备 + 应用是 App Store 版本
2
3
4
5
这两个 API 的革命性:
- 认证由设备硬件级保证(不是软件检查)。
- 私钥永远在 TEE/Secure Enclave(不可被攻击者复制)。
- 服务端能"零信任"地验证客户端真实性。
适用场景:金融转账、游戏防外挂、防机器人注册等高安全场景。
探索性思考:为什么 App Attest 是"客户端完整性的终局答案"? 因为它把"信任"建立在硬件级密钥上,而不是"应用代码自己声称"。攻击者改了应用代码 → Apple/Google 服务器拒绝签发 token → 服务端拒绝服务。这条信任链所有环节都用密码学保证,不依赖应用代码的诚实性。这是密码学+硬件+协议的完美结合,是普通"客户端校验"的根本性升级。
# 12.4 SRI 与 Web 完整性
SRI(Subresource Integrity):HTML 加载第三方 JS/CSS 时,写明期望的哈希值,浏览器校验:
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
2
3
4
5
浏览器下载 lib.js 后计算 SHA-384,不匹配则拒绝执行——防 CDN 被攻破后投毒。
SRI 的局限:
- 只能校验静态资源,动态 API 数据不行。
- 校验失败时整个文件不可用——必须有 fallback。
- 资源更新时 integrity 哈希必须同步更新。
# 13.跨端安全对照
# 13.1 端到端机制对照
| 机制 | Android | iOS | Web |
|---|---|---|---|
| 传输加密 | HTTPS / OkHttp | HTTPS / URLSession | HTTPS / fetch |
| 证书 pinning | NetworkSecurityConfig | URLSession delegate | 不适用(浏览器管) |
| 存储加密 | EncryptedSharedPreferences / AndroidKeyStore | Keychain(默认 Secure Enclave) | 加密 IndexedDB |
| 代码混淆 | R8 / ProGuard | LLVM 优化(弱) | Terser |
| 代码加固 | 360/乐固/几维(成熟生态) | 不需要(系统签名) | WebAssembly(部分) |
| Root/越狱检测 | RootBeer / 自实现 | 越狱检测库 | N/A(沙箱足够) |
| 完整性校验 | APK 签名 + Play Integrity | App Attest | SRI |
| 抓包防护 | 证书 pinning + VPN 检测 | 同 | N/A |
# 13.2 防护强度对比
| 维度 | Android | iOS | Web |
|---|---|---|---|
| 反编译难度 | 低(DEX 易反编译) | 中(Mach-O + 优化) | 极低(明文 JS) |
| 系统沙箱强度 | 中(root 后失效) | 高(越狱率 < 0.1%) | 高(浏览器隔离) |
| 密钥保护 | 中(除非有 StrongBox) | 极高(Secure Enclave) | 弱(IndexedDB 可读) |
| 用户改包风险 | 高 | 极低 | N/A |
| 第三方加固生态 | 极成熟 | 不需要 | 不需要 |
| 平台审核强度 | Play 自动化扫描 | App Store 人工审核 | 浏览器扩展审核 |
# 13.3 统一启示
- iOS 天然比 Android 安全——Secure Enclave + 严格沙箱 + 人工审核三板斧。
- Android 需要更多"应用层加固"补足平台不足——加固/Root 检测/RootBeer 等。
- Web 安全的天花板低——明文 JS + 用户可装扩展,所以 Web 安全核心是"服务端为权威"。
- 跨端的统一原则是"零信任"——无论平台,客户端都不能完全信任,服务端必须做权威校验。
§14-§16 给出三层治理的具体方案。
# 14.治理一层基线
第一层基线 = 不做就是事故——所有应用都必须做这一层,覆盖 80% 的安全风险。本章给出 4 个不可商量的最小集。
# 14.1 HTTPS 全启用
核心命题:没有 HTTP 例外。所有 API 必须 HTTPS,包括内部接口、调试接口、第三方接口。
工程实践:
- Android:
NetworkSecurityConfig.xml设cleartextTrafficPermitted=false(默认 Android 9+ 已经是 false)。 - iOS:保留默认 ATS 配置(强制 HTTPS)。
- Web:
Strict-Transport-Securityheader(HSTS)强制浏览器走 HTTPS。
关键陷阱:
- 不要为"测试方便"开 HTTP 例外——线上忘了关就是事故。
- 不要为"个别 CDN"开 HTTP 例外——一个洞就够攻破整个应用。
性能代价:§17.3 实验 已证明——HTTPS 长连接后续请求与 HTTP 几乎无差异。所谓"HTTPS 慢"是没用长连接的误解。
# 14.2 敏感数据加密
敏感数据"二八原则":20% 的敏感字段产生 80% 的合规风险——优先把这 20% 加密。
必加密清单:
| 数据 | 必加密原因 | 推荐方案 |
|---|---|---|
| 用户密码(如有缓存) | 法规强制 | Argon2 / bcrypt + salt |
| 登录 token | 被盗即冒用 | EncryptedSP / Keychain |
| 用户身份证、手机号 | 个人信息保护法 | EncryptedSP / 加密 DB |
| 用户支付信息(卡号末四位以外) | PCI-DSS 强制 | Keychain / Keystore |
| API 私钥(如有客户端密钥) | 反编译即泄露 | Keystore(推导式) |
| 用户聊天/输入历史 | 隐私合规 | 加密 DB |
不必加密:
- UI 状态(Tab 选中、滚动位置)。
- 缓存的图片、视频。
- 公开的列表数据。
加密方案模板:
// Android
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sp = EncryptedSharedPreferences.create(
context, "secure_prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sp.edit().putString("user_phone", phone).apply()
2
3
4
5
6
7
8
9
10
// iOS
let query: [String: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: "user_phone",
kSecValueData: phone.data(using: .utf8)!,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
]
SecItemAdd(query as CFDictionary, nil)
2
3
4
5
6
7
8
# 14.3 API 签名防重放
HTTPS 不防重放——攻击者抓到一次合法请求后可以反复重发(薅羊毛、刷数据)。
API 签名方案(HMAC + 时间戳 + nonce):
请求体:
{
"user_id": 123,
"action": "claim_coupon",
"amount": 50
}
发送时附加:
timestamp: 1735000000 ← 当前秒级时间戳
nonce: "abc123xyz" ← 随机串(防同一时间戳的重放)
signature: hmac_sha256(secret, timestamp + nonce + json(body))
服务端校验:
1. 检查 timestamp 与服务端时间差 < 5 分钟(过期请求拒绝)
2. 检查 nonce 在 5 分钟窗口内未使用过(Redis 记录)
3. 用 secret 重算 signature 验签
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
关键问题:secret 怎么管理?
- 服务端持有 secret:每个用户独立 secret,登录后下发给客户端(短期)。
- 客户端不存长期 secret:登录态过期后 secret 销毁。
- secret 不写入日志/不入备份。
# 14.4 密钥不入仓
经典错误:
// 反例:密钥硬编码
const val API_KEY = "sk_live_abc123xyz..." // 反编译后立即泄露
const val AES_KEY = "ourSecretKey1234" // 同上
2
3
正确做法:
| 密钥类型 | 存放方式 |
|---|---|
| 服务端 API key | 永远不放客户端(服务端中转) |
| 客户端 AES master key | 系统 Keystore/Keychain 生成,从不离开密钥库 |
| 客户端临时 token | 登录后从服务端获取,用完销毁 |
| 第三方 SDK key | 配置文件(不入仓库),CI 注入 |
| 证书 pinning 哈希 | 可硬编码(公开信息,知道哈希也无法伪造证书) |
CI 注入流程:
GitLab/Jenkins 安全变量:
- 存储 ${API_KEY_PROD}
- 构建时通过环境变量注入
- BuildConfig 自动生成 BuildConfig.API_KEY = "${API_KEY_PROD}"
2
3
4
探索性思考:硬编码"假"密钥行不行? 不行。攻击者反编译看到
API_KEY = "sk_live_..."时,会立即去试——即使你刚把它废弃了,"试一下"的成本极低,"找到能用的"概率不为零。任何看起来像密钥的字符串都不应该出现在代码里。
# 15.治理二层强化
第二层强化 = 风险驱动——金融/游戏/电商等业务必做,工具类视情况。覆盖 95% 风险。
# 15.1 代码混淆与加固
分层策略:
全代码:R8 混淆(开启 fullMode) ← 基线,零代价
关键模块:DEX 加密 + Native 加固 ← 中代价,覆盖 90% 关键代码
核心算法:VMP 虚拟机保护 ← 高代价,仅 1% 极敏感代码
2
3
加固选型决策树:
业务是否真的需要加固?
├── 工具类应用 ──▶ 不需要(混淆够了)
├── 普通 C 端应用 ──▶ DEX 加密足矣
├── 金融/支付 ──▶ DEX + Native 加固
└── 游戏防外挂 ──▶ VMP 关键算法
2
3
4
5
实测数据(§17.2):
- A 厂 DEX 加密:包 +12MB,启动 +180ms。
- B 厂 Native 加固:包 +6MB,启动 +90ms。
- C 厂 VMP:包 +20MB,启动 +400ms。
选型时必做:
- 真机 benchmark(不只看宣传强度)。
- 兼容性测试(部分加固在某些 ROM 会崩)。
- 线上灰度(5% 用户先上)。
# 15.2 完整性校验
参考 §12 的完整链路。在治理层面,应用启动时做一次校验:
class App : Application() {
override fun onCreate() {
super.onCreate()
if (!verifyIntegrity()) {
// 不要直接退出!上报服务端 + 风控降级
riskReport(RiskType.INTEGRITY_FAILURE)
}
}
private fun verifyIntegrity(): Boolean {
val sigHash = computeSelfSignatureHash()
return sigHash == EXPECTED_HASH // 同时上报给服务端二次校验
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
关键工程点:客户端校验失败不阻断用户(避免误伤),由服务端综合判定。
# 15.3 Root 越狱检测
详见 §11.2——检测 + 上报,不阻断。
val deviceRisk = DeviceRisk(
isRooted = checkRoot(),
isEmulator = checkEmulator(),
isHooked = checkHook(),
isDebugging = checkDebug(),
isProxy = checkProxy()
)
api.reportDeviceRisk(deviceRisk) // 服务端按业务策略处理
2
3
4
5
6
7
8
服务端处理示例:
| 风险因子 | 普通用户 | 大额支付 | 敏感操作 |
|---|---|---|---|
| 全部正常 | 正常 | 正常 | 正常 |
| Root 单项 | 正常 | 二次验证 | 拒绝 |
| Hook 检测 | 正常 | 拒绝 | 拒绝 |
| 多项叠加 | 风控关注 | 拒绝 | 拒绝 |
# 15.4 风险用户分级
服务端基于客户端上报 + 行为模式做综合分级:
| 等级 | 特征 | 限制 |
|---|---|---|
| L0 安全 | 无任何风险信号,行为正常 | 全功能 |
| L1 关注 | 单项风险(仅 Root) | 大额业务二次验证 |
| L2 风险 | 多项风险或异常行为 | 拒绝高风险业务,加强人机校验 |
| L3 拦截 | 高度疑似攻击(如批量注册) | 拒绝服务,人工审核解封 |
# 16.治理三层对抗
第三层对抗 = 高强度场景——金融、游戏、防外挂、IP 保护。覆盖剩余 5% 但价值最高的风险。这一层不是所有团队都需要做。
# 16.1 VMP 与 WhiteBox
何时上 VMP:
- 核心算法(订单计费、风控规则、密钥派生)。
- IP 高价值代码(独家算法)。
- 反外挂关键判定。
VMP 的"精准化"原则:
错误用法:整个应用 VMP → 启动 +400ms,性能崩溃
正确用法:仅核心 5-10 个函数 VMP → 启动 +50ms,性能正常
2
WhiteBox 加密:把密钥"嵌入"到加密算法的代码中——攻击者反编译看不到独立的密钥。
适用场景:DRM(数字版权管理)、移动支付的本地密钥派生。
不适用大部分应用:
- 实现极复杂,几乎只能买商业方案。
- 性能代价大(几十倍慢于普通 AES)。
- 仍可被破解(专业团队 1-3 个月)。
# 16.2 Hook 检测对抗
参考 §11.3。在第三层做更深入的对抗:
- 多重检测叠加:进程列表 + maps + 端口 + 字节校验,多重失败才判定。
- 检测代码也加 VMP:让"检测代码本身"难被绕过。
- 服务端联动:客户端的"hook 检测结果"上报到服务端,结合行为分析综合判定。
对抗本质:检测代码也会被攻击者逆向、绕过。没有终局,只有持续对抗。
# 16.3 风控与服务端兜底
最强的客户端保护也不如服务端风控:
客户端层防御:
假设 80% 拦截率(被强逆向时)
服务端风控层:
行为模式识别(频率、时序、设备指纹)
规则引擎(限额、白名单、黑名单)
AI 异常检测(对比正常用户分布)
组合后:
总拦截率 = 80% + 95% × 20% = 99%
2
3
4
5
6
7
8
9
10
服务端风控的关键能力:
| 维度 | 含义 |
|---|---|
| 设备指纹 | 跨账号识别同设备 |
| 行为序列 | 用户操作的时序模式 |
| 图谱关联 | 多账号 / 多设备的网络关系 |
| 限频限额 | 接口调用速率、单日金额上限 |
| 机器学习 | 学正常用户分布,标记异常 |
探索性思考:为什么"风控比加固更值"? 三个原因: ① 风控防的是"行为"——攻击者无论怎么逆向客户端,行为模式难伪装。 ② 风控更新更快——服务端配置秒级生效,客户端加固要发版。 ③ 风控针对性更强——能精确识别"专业黑产"vs"被引诱的小白用户",区分对待。
大型互联网公司的安全投入比例大致是:客户端加固 30%、服务端风控 70%。这个比例反映了 ROI 的真实分布。
# 17.求证实验
# 17.1 实验一:算法选择
Step 1 — 原始观察:工程师常用 RSA 加密大数据(错误用法),导致 API 慢。对比正确做法(AES + RSA 协商)能省多少?
Step 2 — 提出疑问:不同加密方案对 1MB 数据的总耗时差异多大?
Step 3 — 设计实验:
| 方案 | 描述 |
|---|---|
| A 全 RSA | 直接 RSA-2048 加密 1MB(错误,分块) |
| B RSA + AES | RSA-2048 加密 AES key(32B),AES 加密 1MB |
| C 纯 AES | 全 AES(密钥已经协商好) |
| D ECDH + AES | ECDH 协商密钥,AES 加密 |
Step 4 — 实测数据:
| 方案 | 总耗时 |
|---|---|
| A 全 RSA | 不可用(RSA 不支持大数据,分块 ~5s) |
| B RSA + AES | 8.5 ms(RSA 0.5 + AES 8) |
| C 纯 AES | 8 ms |
| D ECDH + AES | 8.2 ms(ECDH 比 RSA 快很多) |
Step 5 — 提炼结论:
大数据必须用 AES,密钥用 RSA 或 ECDH 协商。一次会话内多次加密用同一 AES key(已协商)。ECDH > RSA(同安全等级下更快、密钥更短)。
Step 6 — 边界:
- 国密 SM2(非对称)和 SM4(对称)规律相同。
- 量子加密目前性能差,但是未来必趋势(PQC 后量子密码)。
# 17.2 实验二:加固方案
Step 1 — 原始观察:Android 加固服务很多家(360 / 乐固 / 几维 等),对启动性能影响差异多大?
Step 2 — 设计实验:某 App 同样代码用 3 家加固对比:
| 厂商 | 加固方式 | 包体积增加 | 启动时长 +/- |
|---|---|---|---|
| A 厂 | DEX 加密 + Java 反射 | +12 MB | +180ms |
| B 厂 | Native 加固 + DEX 抽取 | +6 MB | +90ms |
| C 厂 | VMP 虚拟机保护(全包) | +20 MB | +400ms |
| D(自实现) | R8 + 关键模块 VMP | +3 MB | +60ms |
Step 3 — 提炼结论:
加固方案差异巨大:包体积可差 6 倍、启动时长可差 6 倍。精准 VMP(只保护核心算法)比"整包 VMP"性能好 6×、防护强度近似。选型必须实测,不能只看防护强度宣传。
Step 4 — 边界:
- 不同业务(金融/游戏/工具)需要的防护级别不同。
- 加固后必须充分测试兼容性(一些设备可能崩溃)。
- 国产加固服务对国内厂商 ROM 兼容性更好,海外用 Crashlytics 时要注意。
# 17.3 实验三:HTTPS 长连接
Step 1 — 原始观察:HTTPS 公认"慢",但配合长连接到底慢多少?
Step 2 — 设计实验:测试同一接口:
| 配置 | 描述 |
|---|---|
| A | HTTP / 短连接 |
| B | HTTPS / 短连接 |
| C | HTTPS / 长连接(keepalive) |
| D | HTTPS / 长连接 + TLS 1.3 |
Step 3 — 实测数据:
| 配置 | 第一次 | 后续平均 |
|---|---|---|
| A HTTP 短连 | 50ms | 50ms(每次新建) |
| B HTTPS 短连 | 220ms | 220ms |
| C HTTPS 长连 | 220ms(首次) | 38ms |
| D HTTPS 长连 + TLS 1.3 | 120ms(首次 1-RTT) | 38ms |
Step 4 — 提炼结论:
HTTPS 长连接后续请求几乎和 HTTP 一样快(< 10ms 差异)。"HTTPS 慢"是没用长连接的误会。TLS 1.3 还能把首次握手减半(2-RTT → 1-RTT)。
Step 5 — 工程意义:
- 必用 HTTPS(详见
卷四·02 §5.1)。 - 必用长连接(OkHttp/URLSession 默认开)。
- TLS 1.3 + HTTP/2 进一步优化首次。
# 17.4 实验四:密钥库收益
Step 1 — 原始观察:用系统 Keystore vs 自实现 AES(密钥硬编码),安全收益有多大?
Step 2 — 设计实验:用攻击者视角逆向同一应用的两个版本:
| 版本 | 密钥保管 | 攻击难度 |
|---|---|---|
| A | 密钥硬编码 + AES 自实现 | 反编译 5 分钟拿到密钥 |
| B | EncryptedSP / Keychain(系统密钥库) | 理论上无法拿到密钥(除非破 TEE) |
Step 3 — 实测:
| 方案 | 加密耗时 | 数据泄露概率(root 设备) |
|---|---|---|
| A 硬编码 | 50μs | 100% |
| B 软件密钥库 | 200μs | 60%(系统进程攻破后可能拿到) |
| C TEE/Secure Enclave | 500μs | < 1%(需破 TEE) |
Step 4 — 提炼结论:
密钥库的性能代价是 4-10× 加密耗时(仍是亚毫秒级),但安全收益巨大。即使在 root 设备,密钥库仍能保护数据;硬编码密钥则 0 防御力。敏感数据加密必须配合系统密钥库。
Step 5 — 边界:
- 老 Android 设备(API < 23)无 Keystore,只能软件实现。
- iOS 全设备支持 Keychain,无障碍。
# 17.5 实验五:混淆收益
Step 1 — 原始观察:R8/ProGuard 混淆是默认开的,但真的有效吗?
Step 2 — 设计实验:让 3 个安全研究员逆向同一应用,对比不同混淆配置的逆向耗时:
| 配置 | 平均逆向耗时 | 还原代码可读性 |
|---|---|---|
| 不混淆 | 1 小时 | 接近源码 |
| ProGuard 默认 | 4 小时 | 短名但结构清晰 |
| R8 + fullMode | 8 小时 | 短名 + 内联 + 结构破坏 |
| R8 + 字符串混淆 | 12 小时 | 同上 + 关键字符串隐藏 |
Step 3 — 提炼结论:
混淆是性价比最高的代码保护——零代价(构建期完成)、显著提升攻击成本(4-12 倍)。完全不混淆是"开门揖盗"。
Step 4 — 工程意义:
- 默认开 R8 fullMode。
- 关键字符串单独混淆。
- 但混淆只是基线——真正高价值代码还需要加固/VMP。
# 17.6 五大实验启示
算法选择 → AES + ECDH/RSA 协商,不要单 RSA ─┐
│
加固方案 → 精准 VMP 比全包 VMP 性能好 6× │
│
HTTPS 长连接 → 后续请求几乎无开销(< 10ms 差异) ├─▶ 安全 = 选对方案 + 配合优化 + 实测代价 + 系统密钥 + 默认混淆
│
系统密钥库 → 4-10× 性能代价换"100% → < 1%"泄露概率 │
│
混淆收益 → 零代价让攻击成本 4-12× ─┘
2
3
4
5
6
7
8
9
统一启示:
- 算法/方案选错代价巨大:必须按业务匹配。
- 数据驱动:每个方案上线前必须实测。
- 安全和性能不矛盾:选对方案能同时达成。
- 系统提供的能力是免费午餐:不用 Keystore/Keychain 就是把礼物扔掉。
- 混淆是基线、加固按风险、VMP 仅核心:分层投入 ROI 最优。
# 18.实战案例
# 18.1 跨端同构案例
背景:某金融应用希望"全面升级安全"但担心性能变差。
度量与归因:
- HTTPS 已经全启用,但部分老接口未用长连接。
- 本地数据存 SharedPreferences 明文。
- API 没有签名/时间戳。
- 客户端硬编码了"AES 主密钥"。
假设与求证:提出统一假设:"必做基线 + 关键场景加强"。
修复:
- 全平台启用 HTTPS 长连接 + TLS 1.3。
- 敏感数据迁移到 EncryptedSP / Keychain。
- API 全部加签名 + 时间戳 + nonce。
- 把硬编码密钥换成 Keystore 派生(启动时生成、不入仓)。
验证:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 安全 audit 通过率 | 60% | 95% |
| API 平均响应 | 280ms | 270ms(HTTPS 长连接基本无代价) |
| 启动时长 | 1.5s | 1.5s(无变化) |
| 反编译能拿到密钥 | 是 | 否 |
统一启示:做对方案,安全和性能不矛盾。
# 18.2 平台特异案例
背景:Android 应用接入加固后冷启动慢 400ms,用户流失。
现象:加固前 1.2s,加固后 1.6s。
度量与归因:加固方案 = VMP(虚拟机保护,最强防护但代价大),且整包 VMP。
假设与求证:业务不需要 VMP 级别防护,**精准 VMP(仅核心算法)**已足够。
实验:仅对核心订单计费 + 风控判定函数(共 8 个函数)用 VMP,其余只 R8 + Native 加固。
结果:启动恢复到 1.3s(仅 +100ms 代价),核心防护强度无降级。
修复:换"精准 VMP"+ 其他模块 R8 + 关键 SO Native 加固。
验证:启动时长达标 + 防护强度仍可接受。
边界:金融/游戏等高安全需求保留更广 VMP 范围。
# 18.3 反作弊案例
背景:某游戏应用大量外挂用户,传统 Root 检测拦不住。
度量与归因:
- Root 检测 90% 能识别 root 设备,但发现就退出 → 误伤普通用户 5%。
- 真正外挂用户用 Magisk Hide 隐藏 root,检测 0% 命中。
假设:客户端检测+服务端风控才是反作弊正确路径。
修复:
- 客户端:Root/Hook 检测 + Play Integrity API 上报,不阻断用户。
- 服务端:基于行为模式(操作频率、移动速度、得分曲线)综合判定。
- 风控决策:风险用户匹配同水平对手、限制竞技排名、不发奖。
验证:
| 指标 | 改前 | 改后 |
|---|---|---|
| 外挂识别率 | 0%(Magisk Hide 绕过) | 85%(行为模式无法伪装) |
| 普通用户误伤 | 5%(被踢) | 0% |
| 用户投诉 | 高 | 显著降低 |
统一启示:客户端拦截 + 服务端风控的组合,比"客户端死防"效果好得多。
# 19.防劣化体系
# 19.1 三道防线总览
开发期 ──▶ 编译期 / CI ──▶ 上线期 / 运行期
│ │ │
▼ ▼ ▼
[Lint] [安全 audit] [监控 + 告警]
2
3
4
# 19.2 编码期 Lint
强制规则(违反则报错):
- HTTP(非 HTTPS)API 调用 → 错误。
- 用 RSA 加密大数据 → 错误。
- 密钥硬编码在代码里 → 错误。
- 用
MD5/SHA-1做安全哈希 → 警告。 - 敏感数据存 SharedPreferences/UserDefaults 明文 → 警告。
trustAllCertificates/disableSSL→ 错误。eval/Runtime.exec用户输入 → 错误。
# 19.3 CI 与线上 SLO
CI 卡口:
- 静态扫描(敏感字符串/弱算法)。
- 加密接口性能基准回归。
- 加固后包大小/启动 baseline。
- 依赖库 CVE 扫描(Snyk / OWASP Dependency-Check)。
线上 SLO:
- HTTPS 失败率 < 0.1%。
- 加密耗时 P99 < 阈值。
- 安全告警率(Root/Hook 检测)合理范围(突变就告警)。
- API 签名失败率 < 0.01%(异常突增 = 风控事件)。
# 20.跨平台速查
# 20.1 工具速查
| 平台 | 加密 | 安全存储 | 完整性 | 反编译/Hook 检测 |
|---|---|---|---|---|
| Android | Cipher / Keystore | EncryptedSharedPreferences | PackageInfo signatures + Play Integrity | RootBeer / 自检 |
| iOS | CryptoKit | Keychain (Secure Enclave) | App Attest | libfishhook 检测 |
| Web | crypto.subtle | 加密 IndexedDB | SRI | N/A |
# 20.2 关键 API 速查
| 操作 | Android | iOS | Web |
|---|---|---|---|
| 对称加密 | Cipher.getInstance("AES/GCM/NoPadding") | AES.GCM.seal | crypto.subtle.encrypt |
| 非对称加密 | Cipher.getInstance("RSA/ECB/OAEPPadding") | SecKeyCreateEncryptedData | crypto.subtle.encrypt(rsa) |
| 哈希 | MessageDigest.getInstance("SHA-256") | SHA256.hash | crypto.subtle.digest |
| 密码哈希 | Argon2 库 | CryptoKit + 自实现 | argon2-browser |
| 安全存储 | EncryptedSharedPreferences | Keychain Services | 加密 IndexedDB |
| 长连接 HTTPS | OkHttp 默认 | URLSession 默认 | fetch 默认 |
| 完整性证明 | Play Integrity API | DCAppAttestService | SRI |
# 21.总结与延伸
# 21.1 五条核心原则
- 安全是成本对抗:让攻击成本 > 收益,不追求绝对安全。
- 必做基线先做满:HTTPS / AES / 密钥库 / 签名是地板(§14)。
- 算法选对省 90% 性能:AES 加大数据 + ECDH/RSA 协商小密钥。
- 加固方案精准化:VMP 只保护核心 1-5%,整包 VMP 是反模式。
- 链条最弱环决定整体:不能"部分做"——一个不加密 API 让全应用 HTTPS 失效。
# 21.2 五个常见误区
- ❌ "安全必牺牲性能":选对方案不冲突(HTTPS 长连接 = 几乎零代价)。
- ❌ "AES 越长越安全越慢":AES-128 已经够安全,AES-256 主要防量子。
- ❌ "加固越强越好":性能代价大,且 VMP 只对 1% 代码有意义。
- ❌ "客户端加密 = 端到端加密":客户端密钥必然泄露,"客户端加密"是伪安全。
- ❌ "自实现算法更安全":自实现密码学 = 必有漏洞,工业标准是数十年集体验证的。
# 21.3 延伸阅读
- OWASP Mobile Top 10:移动端安全的权威清单。
- OWASP API Security Top 10:API 设计安全规范。
- 《Cryptography Engineering》(Ferguson、Schneier):密码学工程的圣经。
- 《Real-World Cryptography》(David Wong):现代密码学实战。
- WWDC: Core Data Security:iOS 数据安全官方指南。
- Google: Android Application Security Best Practices:Android 安全最佳实践。
- NIST SP 800-57:密钥管理国际标准。
# 一句话总结
安全是性能的隐形成本,但选对方案两者并不矛盾。 HTTPS 长连接、AES + ECDH 混合、系统密钥库、API 签名 是无代价的"必做基线"——大部分团队的问题不是"做得不够强",而是"基线没做满"。 加固和 VMP 要按业务风险匹配、精准应用——不是所有代码都需要最强保护,只有核心 1-5% 才值得。 最强的客户端保护也不如服务端风控:客户端拦 80% 攻击者,服务端兜剩下的 19%——这是工业级安全的真实分布。