编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 性能优化实践

    • README
    • 公共方法论

    • 体系建设篇

    • 资源专项篇

    • 流水线专项

    • 业务专项篇

    • 交付防御篇

      • 崩溃捕获设计实践
      • 包体积与资源治理
      • 应用安全性能权衡
        • 01.阅读说明
        • 02.贯穿案例
          • 2.1 案例背景
          • 2.2 经验派 8 周折腾
          • 2.3 方法派 14 天闭环
          • 2.4 上线效果
          • 2.5 案例串联全文
        • 03.安全思想原理
          • 3.1 成本对抗思想
          • 3.2 纵深防御思想
          • 3.3 最小权限思想
          • 3.4 零信任思想
          • 3.5 安全性能权衡
        • 04.威胁模型分析
          • 4.1 STRIDE 六类威胁
          • 4.2 攻击面盘点
          • 4.3 攻击者画像
          • 4.4 反直觉问题清单
        • 05.密码学第一性
          • 5.1 对称加密原理
          • 5.2 非对称加密原理
          • 5.3 哈希与签名
          • 5.4 混合加密的必然
          • 5.5 密钥管理本质
        • 06.度量与采集
          • 6.1 三类采集方案
          • 6.2 各方案盲区
          • 6.3 跨平台采集
          • 6.4 数据可信度
        • 07.归因决策树
          • 7.1 安全性能决策树
          • 7.2 加密代价归因
          • 7.3 加固代价归因
        • 08.传输安全全链路
          • 8.1 传输威胁的本质
          • 8.2 TLS 握手原理
          • 8.3 证书校验机制
          • 8.4 双向认证 mTLS
          • 8.5 中间人攻击防御
        • 09.存储安全全链路
          • 9.1 存储威胁的本质
          • 9.2 沙箱与文件权限
          • 9.3 系统密钥库原理
          • 9.4 数据加密落地
        • 10.代码保护全链路
          • 10.1 反编译威胁本质
          • 10.2 代码混淆原理
          • 10.3 加固方案对比
          • 10.4 VMP 虚拟机保护
        • 11.运行时防御全链路
          • 11.1 动态攻击的本质
          • 11.2 Root 与越狱检测
          • 11.3 Hook 检测原理
          • 11.4 调试与抓包检测
        • 12.完整性校验全链路
          • 12.1 篡改威胁本质
          • 12.2 包签名校验
          • 12.3 设备完整性证明
          • 12.4 SRI 与 Web 完整性
        • 13.跨端安全对照
          • 13.1 端到端机制对照
          • 13.2 防护强度对比
          • 13.3 统一启示
        • 14.治理一层基线
          • 14.1 HTTPS 全启用
          • 14.2 敏感数据加密
          • 14.3 API 签名防重放
          • 14.4 密钥不入仓
        • 15.治理二层强化
          • 15.1 代码混淆与加固
          • 15.2 完整性校验
          • 15.3 Root 越狱检测
          • 15.4 风险用户分级
        • 16.治理三层对抗
          • 16.1 VMP 与 WhiteBox
          • 16.2 Hook 检测对抗
          • 16.3 风控与服务端兜底
        • 17.求证实验
          • 17.1 实验一:算法选择
          • 17.2 实验二:加固方案
          • 17.3 实验三:HTTPS 长连接
          • 17.4 实验四:密钥库收益
          • 17.5 实验五:混淆收益
          • 17.6 五大实验启示
        • 18.实战案例
          • 18.1 跨端同构案例
          • 18.2 平台特异案例
          • 18.3 反作弊案例
        • 19.防劣化体系
          • 19.1 三道防线总览
          • 19.2 编码期 Lint
          • 19.3 CI 与线上 SLO
        • 20.跨平台速查
          • 20.1 工具速查
          • 20.2 关键 API 速查
        • 21.总结与延伸
          • 21.1 五条核心原则
          • 21.2 五个常见误区
          • 21.3 延伸阅读
        • 一句话总结
      • 弱网极端环境治理
  • 程序编程原理

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

  • 专栏
  • 性能优化实践
  • 交付防御篇
杨充
2026-05-27
目录

应用安全性能权衡

# 应用安全与性能权衡

📊 学习成本预估 | 难度:⭐⭐⭐⭐(4/5)| 阅读:约 40 分钟 | 实操:3 小时 🔗 前置阅读:全栏 | ➡️ 后续延伸:—

# 目录介绍

  • 01.阅读说明
  • 02.贯穿案例
    • 2.1 案例背景
    • 2.2 经验派 8 周折腾
    • 2.3 方法派 14 天闭环
    • 2.4 上线效果
    • 2.5 案例串联全文
  • 03.安全思想原理
    • 3.1 成本对抗思想
    • 3.2 纵深防御思想
    • 3.3 最小权限思想
    • 3.4 零信任思想
    • 3.5 安全性能权衡
  • 04.威胁模型分析
    • 4.1 STRIDE 六类威胁
    • 4.2 攻击面盘点
    • 4.3 攻击者画像
    • 4.4 反直觉问题清单
  • 05.密码学第一性
    • 5.1 对称加密原理
    • 5.2 非对称加密原理
    • 5.3 哈希与签名
    • 5.4 混合加密的必然
    • 5.5 密钥管理本质
  • 06.度量与采集
    • 6.1 三类采集方案
    • 6.2 各方案盲区
    • 6.3 跨平台采集
    • 6.4 数据可信度
  • 07.归因决策树
    • 7.1 安全性能决策树
    • 7.2 加密代价归因
    • 7.3 加固代价归因
  • 08.传输安全全链路 ⭐
    • 8.1 传输威胁的本质
    • 8.2 TLS 握手原理
    • 8.3 证书校验机制
    • 8.4 双向认证 mTLS
    • 8.5 中间人攻击防御
  • 09.存储安全全链路 ⭐
    • 9.1 存储威胁的本质
    • 9.2 沙箱与文件权限
    • 9.3 系统密钥库原理
    • 9.4 数据加密落地
  • 10.代码保护全链路 ⭐
    • 10.1 反编译威胁本质
    • 10.2 代码混淆原理
    • 10.3 加固方案对比
    • 10.4 VMP 虚拟机保护
  • 11.运行时防御全链路 ⭐
    • 11.1 动态攻击的本质
    • 11.2 Root 与越狱检测
    • 11.3 Hook 检测原理
    • 11.4 调试与抓包检测
  • 12.完整性校验全链路 ⭐
    • 12.1 篡改威胁本质
    • 12.2 包签名校验
    • 12.3 设备完整性证明
    • 12.4 SRI 与 Web 完整性
  • 13.跨端安全对照
    • 13.1 端到端机制对照
    • 13.2 防护强度对比
    • 13.3 统一启示
  • 14.治理一层基线 ⭐
    • 14.1 HTTPS 全启用
    • 14.2 敏感数据加密
    • 14.3 API 签名防重放
    • 14.4 密钥不入仓
  • 15.治理二层强化 ⭐
    • 15.1 代码混淆与加固
    • 15.2 完整性校验
    • 15.3 Root 越狱检测
    • 15.4 风险用户分级
  • 16.治理三层对抗 ⭐
    • 16.1 VMP 与 WhiteBox
    • 16.2 Hook 检测对抗
    • 16.3 风控与服务端兜底
  • 17.求证实验 ⭐
    • 17.1 实验一:算法选择
    • 17.2 实验二:加固方案
    • 17.3 实验三:HTTPS 长连接
    • 17.4 实验四:密钥库收益
    • 17.5 实验五:混淆收益
    • 17.6 五大实验启示
  • 18.实战案例
    • 18.1 跨端同构案例
    • 18.2 平台特异案例
    • 18.3 反作弊案例
  • 19.防劣化体系
    • 19.1 三道防线总览
    • 19.2 编码期 Lint
    • 19.3 CI 与线上 SLO
  • 20.跨平台速查
    • 20.1 工具速查
    • 20.2 关键 API 速查
  • 21.总结与延伸
    • 21.1 五条核心原则
    • 21.2 五个常见误区
    • 21.3 一句话总结

# 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   ──▶  不值得攻击(实质安全)
攻击者成本
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 签名]  ── 突破 ──▶  [服务端风控]  ── 突破 ──▶ [审计追溯]
                                                                                              │
                                                                                              ▼
                                                                                          及时发现+止损
1
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 零信任思想

核心命题:永远不要信任任何输入、任何环境、任何客户端——即使是"你自己的"客户端。

传统思想:客户端是"可信的",服务端为客户端提供服务
零信任:    客户端是"敌对的",服务端必须验证一切
1
2

关键含义:

  • 客户端是不可信的——所有客户端都可能被破解、被改包、被 hook。
  • 客户端校验等于没校验——所有业务规则必须服务端校验。前端表单校验只是 UX 优化,不是安全。
  • 客户端密钥必然泄露——不要把"只能客户端持有"的密钥放进客户端。

典型反例:

  • 客户端校验金额是否合法 → 服务端不再校验:攻击者改包就能下 1 分钱订单。
  • 客户端"标记"自己是 VIP → 服务端信任:攻击者直接传 is_vip=true。
  • 客户端持有"主密钥"加密上传 → 服务端解密:客户端被破解 = 全部数据泄露。

零信任的具体落地:

  • 服务端为权威:所有业务规则、价格计算、权限判定都在服务端。
  • 客户端密钥派生:客户端持有的密钥从用户登录态派生(如 PBKDF2(用户密码) → 临时密钥),不是预置主密钥。
  • API 签名:服务端验签确保请求未被篡改(详见 §14.3)。
  • 行为风控:服务端基于行为模式识别异常(如同一账号 1 秒下 100 单)。

探索性思考:为什么"客户端加密"在安全里基本无意义? 因为客户端代码 = 公开代码(攻击者可以反编译)。客户端持有的密钥就是公开密钥。客户端加密能"防普通用户看包"(HTTPS 抓包看到密文),但防不了真正的攻击者——他们逆向出密钥后,所有"加密数据"都是明文。客户端加密 ≠ 端到端加密。前者只是"传输加密的延伸",后者是"用户密码派生密钥"——两者完全不同。

# 3.5 安全性能权衡

核心命题:所有安全措施都有性能代价,工程的目标不是"无限提升安全",而是"匹配业务风险等级"。

代价的具体表现:

   加密 = CPU 时间 + 内存
   加固 = 包体积 + 启动时长 + 可能的 JIT 失效
   校验 = 启动时长 + IO
   反调试 = 运行时检测开销
   隔离 = IPC 开销
   通信 = 握手延迟(首次)
1
2
3
4
5
6

权衡的三层模型:

   ┌──────────────────────────────────────┐
   │ 必须做(关乎用户隐私 / 金钱)             │
   │ - HTTPS 全站                           │
   │ - 用户密码 hash + salt                  │
   │ - 关键 API 签名                          │
   │ - 敏感数据本地加密存储                    │
   ├──────────────────────────────────────┤
   │ 视风险做(防破解 / 反作弊)              │
   │ - 代码混淆                              │
   │ - 加固                                  │
   │ - 反调试                                │
   │ - 完整性校验                            │
   ├──────────────────────────────────────┤
   │ 一般不做(成本 > 收益)                  │
   │ - WhiteBox 加密                         │
   │ - 完全自定义协议                         │
   │ - 极致防 hook                           │
   └──────────────────────────────────────┘
1
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 反直觉问题清单

带着这些问题阅读后续章节:

  1. AES 是不是越长 key 越安全越慢?
  2. RSA 真的能加密大数据吗?
  3. 加固一定让启动慢吗?
  4. 防 root / 防越狱真有用吗?
  5. WhiteBox 加密能完全防破解吗?
  6. 国密算法比 AES 慢多少?
  7. HTTPS 必须双向认证吗?
  8. 客户端加密是不是更安全?

# 05.密码学第一性

密码学是安全的"地基"——不理解原理就写安全代码 = 在沙地上盖楼。本章用最少的数学讲清楚四个核心机制:对称加密、非对称加密、哈希、签名。

# 5.1 对称加密原理

对称加密:加密密钥 = 解密密钥(双方共享同一个秘密)。

明文 + 密钥 K  ──加密──▶  密文
密文 + 密钥 K  ──解密──▶  明文
1
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(防篡改),工业标准
1
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   ──解密──▶  明文

公钥可以公开,私钥严守秘密
1
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,输出全变
1
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),让暴力破解变慢
1
2
3
4
5
6
7

数字签名:用私钥对消息生成签名,任何人用公钥能验证——证明消息来自私钥持有者:

消息 + 私钥 SK ──签名──▶ signature
消息 + signature + 公钥 PK ──验证──▶ 真伪

性质:
1. 不可伪造(没私钥签不出)
2. 不可否认(只有私钥持有者能签)
3. 完整性(消息改了签名失效)
1
2
3
4
5
6
7

代表算法:RSA 签名 / ECDSA / Ed25519。

探索性思考:为什么"签名"和"加密"是不同的? 直觉上"用私钥加密 = 签名",但这是错的:

  • 加密目的是保密:用对方的公钥加密,只有对方私钥能解。
  • 签名目的是认证:用自己的私钥签,所有人都能用公钥验证。

RSA 在数学上恰好可以互换(私钥能"加密",公钥能"解密"),但ECC 加密和 ECC 签名是完全不同的算法。在工程上永远要用专门的"sign/verify" API,不要用 "encrypt/decrypt" 模拟签名。

# 5.4 混合加密的必然

为什么所有现代密码协议(TLS / PGP / 微信加密协议)都是"对称 + 非对称"混合?

加密大数据:
   非对称加密慢 1000×、限制大小
   对称加密快、无大小限制
   ↓
   只用对称:怎么把密钥安全地告诉对方?
   只用非对称:太慢
   ↓
   混合方案:
   1. 用非对称交换/协商一个对称密钥(少量数据,慢一点没事)
   2. 用对称密钥加密实际数据(大量数据,必须快)
1
2
3
4
5
6
7
8
9
10

TLS 1.3 的简化流程:

1. 客户端 → 服务端:发起握手 + 客户端随机数
2. 服务端 → 客户端:服务端证书(含公钥)+ 服务端随机数
3. 客户端用 ECDH 与服务端协商出共享密钥
4. 后续通信用 ECDH 协商出的对称密钥(AES-GCM)加密
1
2
3
4

这就是为什么 §17.1 实验 中"全 RSA"方案不可用、"RSA + AES" 方案是工业标准。

# 5.5 密钥管理本质

密码学的最大难题不是算法选择,是密钥管理——再好的算法,密钥泄露就全完蛋。

密钥的全生命周期:
   生成 ──▶ 存储 ──▶ 使用 ──▶ 轮换 ──▶ 销毁

每个阶段都有泄露风险
1
2
3
4

关键原则:

  • 密钥不入代码仓:硬编码密钥反编译就拿到(详见 §14.4)。
  • 密钥不入日志:日志里打印密钥 = 把密钥送给所有 SRE。
  • 密钥不入备份:明文备份等于多个泄露点。
  • 密钥用专门设备保管:客户端用系统 Keystore/Keychain(详见 §9.3),服务端用 HSM/KMS。
  • 密钥定期轮换:长期使用同密钥风险累积,应支持平滑轮换。
  • 密钥分级:根密钥保护工作密钥、工作密钥保护数据密钥(密钥分层)。

探索性思考:为什么"最好的密钥保护是不保护"? 这不是悖论——意思是让客户端根本不持有长期密钥。例如:

  • 客户端只保留登录态 token(短期、可吊销)。
  • 真正的加密密钥从用户密码派生(PBKDF2/Argon2),不存任何地方。
  • 支付密钥服务端临时下发,用完即销毁。

"没存的密钥不会泄露"——这是密钥管理的至高境界。


# 06.度量与采集

# 6.1 三类采集方案

   ① 性能开销监控(加密/校验耗时)
   ② 安全告警(异常行为/风险事件)
   ③ 攻击模拟(线下渗透测试)
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 关闭
                       └─ 灰度环境配置
1
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 加密)│
  │ ◀────────────────────────────────▶│
1
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 在我的信任列表里吗?
1
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:
   客户端 ──证书校验──▶ 服务端身份可信
   服务端 ──证书校验──▶ 客户端身份可信
1
2
3
4
5
6
7

适用场景:

  • B2B API:合作方调用,必须双向认证。
  • 核心金融 API:登录态之上还要"设备身份"。
  • 企业内网:员工设备访问内部系统。

不适用场景:

  • 普通 C 端应用——客户端证书无法安全保管(第 3.4 节零信任:客户端可被破解,证书必然泄露)。

# 8.5 中间人攻击防御

中间人(MITM)攻击:攻击者插入到客户端和服务端之间,对双方都伪装成"对端":

正常:
   客户端 ────HTTPS───▶ 服务端
   
中间人:
   客户端 ────HTTPS───▶ [攻击者代理] ────HTTPS───▶ 服务端
                            │
                            ▼
                      解密、读取、改写、再加密
1
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)即使知道路径也读不到
1
2
3
4
5

iOS 沙箱模型:

每个应用有独立的 sandbox container
   ↓
应用只能访问自己 container 内的目录
   ↓
跨 App 数据交换必须通过系统 API(UIDocumentPicker/AppGroup)
1
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 里用密钥加密
                  ↓
           返回密文给应用
                  ↑
           密钥从未出现在应用进程的内存里
1
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()
1
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)
1
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 → 解密数据
1
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/算法常数)
                        └─ 改包重发(破解版/外挂)
1
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 { ... }
   }
1
2
3
4
5
6
7
8
9

混淆能解决什么:

  • ✅ 让攻击者难"读懂代码意图"(看到 a.b() 不知道是登录还是支付)。
  • ✅ 减小 APK 体积(短名比长名小)。
  • ✅ 提高静态分析成本。

混淆不能解决什么:

  • ❌ 防动态调试——运行时仍能看到字节码逻辑。
  • ❌ 防字符串提取——明文字符串(API URL/密钥)混淆不掉。
  • ❌ 防关键算法被理解——逻辑结构没变,资深逆向仍能读懂。

Android R8/ProGuard 工程实践:

关键配置:
- 启用 R8(默认开启)
- 不要 -keep 太多(每个 keep 都是混淆漏洞)
- 字符串单独混淆(ProGuard StringObfuscator 插件)
- enableR8FullMode = true(更激进的优化)
1
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)逐条解释执行
1
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()
                                                         │
                                                         ▼
                                                   或不调原方法
1
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 用户:限制大额支付、限制提现、加强人机验证
1
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 改写)
1
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
   ↓ 攻击者下载
   ↓ 反编译、修改业务代码(如去广告、加外挂)
   ↓ 用攻击者自己的证书重新签名
   ↓ 分发到第三方应用市场
   ↓ 用户安装"破解版"
1
2
3
4
5
6

完整性校验的目标:让"被篡改的版本无法在你的服务端正常工作"。

注意:单纯客户端校验"自己的签名"基本无效——攻击者会同时把校验代码改成永远返回 true。必须服务端介入才有效。

# 12.2 包签名校验

Android APK 签名机制:

开发者签名:
   APK 内容 ──hash──▶ 摘要
   摘要 + 开发者私钥 ──签名──▶ APK 签名(写入 META-INF)

验证(Android 系统 + 应用自己):
   APK 内容 ──hash──▶ 摘要1
   APK 签名 + 开发者公钥 ──验证──▶ 摘要2
   摘要1 == 摘要2 → APK 未被改
1
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  // 硬编码的开发者证书哈希
}
1
2
3
4
5
6

关键问题:EXPECTED_SHA256 写在代码里,攻击者反编译后改成自己的证书哈希——校验就被绕过。

正确做法:结合服务端——把签名哈希上报服务端,服务端校验:

客户端:sha256 = 计算自己包的签名 → 上报给服务端(带在每个 API 头里)
服务端:
   if sha256 != expected: 标记设备风险,返回 403 或降级数据
1
2
3

# 12.3 设备完整性证明

Android Play Integrity API(取代 SafetyNet):

应用 ──请求挑战──▶ Google Play 服务
Play 服务 ──验证设备 + 应用包名 + 签名──▶ 生成 token(Google 私钥签名)
应用 ──token──▶ 自己服务端
自己服务端 ──用 Google 公钥验证 token──▶ 确认设备/应用真实
1
2
3
4

iOS App Attest(iOS 14+):

类似机制:
应用 ──请求──▶ Apple 服务器(生成挑战)
设备 Secure Enclave ──签名挑战──▶ token
应用 ──token──▶ 自己服务端
自己服务端 ──用 Apple 公钥验证──▶ 确认设备 + 应用是 App Store 版本
1
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>
1
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-Security header(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()
1
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)
1
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 验签
1
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"        // 同上
1
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}"
1
2
3
4

探索性思考:硬编码"假"密钥行不行? 不行。攻击者反编译看到 API_KEY = "sk_live_..." 时,会立即去试——即使你刚把它废弃了,"试一下"的成本极低,"找到能用的"概率不为零。任何看起来像密钥的字符串都不应该出现在代码里。


# 15.治理二层强化

第二层强化 = 风险驱动——金融/游戏/电商等业务必做,工具类视情况。覆盖 95% 风险。

# 15.1 代码混淆与加固

分层策略:

全代码:R8 混淆(开启 fullMode)          ← 基线,零代价
关键模块:DEX 加密 + Native 加固           ← 中代价,覆盖 90% 关键代码
核心算法:VMP 虚拟机保护                   ← 高代价,仅 1% 极敏感代码
1
2
3

加固选型决策树:

业务是否真的需要加固?
├── 工具类应用 ──▶ 不需要(混淆够了)
├── 普通 C 端应用 ──▶ DEX 加密足矣
├── 金融/支付 ──▶ DEX + Native 加固
└── 游戏防外挂 ──▶ VMP 关键算法
1
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  // 同时上报给服务端二次校验
    }
}
1
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)  // 服务端按业务策略处理
1
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,性能正常
1
2

WhiteBox 加密:把密钥"嵌入"到加密算法的代码中——攻击者反编译看不到独立的密钥。

适用场景:DRM(数字版权管理)、移动支付的本地密钥派生。

不适用大部分应用:

  • 实现极复杂,几乎只能买商业方案。
  • 性能代价大(几十倍慢于普通 AES)。
  • 仍可被破解(专业团队 1-3 个月)。

# 16.2 Hook 检测对抗

参考 §11.3。在第三层做更深入的对抗:

  • 多重检测叠加:进程列表 + maps + 端口 + 字节校验,多重失败才判定。
  • 检测代码也加 VMP:让"检测代码本身"难被绕过。
  • 服务端联动:客户端的"hook 检测结果"上报到服务端,结合行为分析综合判定。

对抗本质:检测代码也会被攻击者逆向、绕过。没有终局,只有持续对抗。

# 16.3 风控与服务端兜底

最强的客户端保护也不如服务端风控:

客户端层防御:
   假设 80% 拦截率(被强逆向时)
   
服务端风控层:
   行为模式识别(频率、时序、设备指纹)
   规则引擎(限额、白名单、黑名单)
   AI 异常检测(对比正常用户分布)
   
组合后:
   总拦截率 = 80% + 95% × 20% = 99%
1
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×                       ─┘
1
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]    [监控 + 告警]
1
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 五条核心原则

  1. 安全是成本对抗:让攻击成本 > 收益,不追求绝对安全。
  2. 必做基线先做满:HTTPS / AES / 密钥库 / 签名是地板(§14)。
  3. 算法选对省 90% 性能:AES 加大数据 + ECDH/RSA 协商小密钥。
  4. 加固方案精准化:VMP 只保护核心 1-5%,整包 VMP 是反模式。
  5. 链条最弱环决定整体:不能"部分做"——一个不加密 API 让全应用 HTTPS 失效。

# 21.2 五个常见误区

  1. ❌ "安全必牺牲性能":选对方案不冲突(HTTPS 长连接 = 几乎零代价)。
  2. ❌ "AES 越长越安全越慢":AES-128 已经够安全,AES-256 主要防量子。
  3. ❌ "加固越强越好":性能代价大,且 VMP 只对 1% 代码有意义。
  4. ❌ "客户端加密 = 端到端加密":客户端密钥必然泄露,"客户端加密"是伪安全。
  5. ❌ "自实现算法更安全":自实现密码学 = 必有漏洞,工业标准是数十年集体验证的。

# 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%——这是工业级安全的真实分布。

上次更新: 2026/06/07, 10:26:12
包体积与资源治理
弱网极端环境治理

← 包体积与资源治理 弱网极端环境治理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式