第2章 混淆原理解析:代码混淆、资源压缩、字符串加密等核心机制
做安卓安全这么多年,我经常被问到:“混淆到底干了什么?” 说白了,混淆不是让你的代码变成密文,而是把人类可读的逻辑变得面目全非。它和加密是两码事——加密要还原,混淆不需要。
这一章,我会从三个核心维度拆解:代码混淆、资源压缩、字符串加密。搞懂它们,你就知道 ProGuard / R8 的配置不是瞎写的。
2.1 代码混淆:把类名变成 a、b、c
代码混淆的核心就一个字:改。把有意义的类名字、方法名字改成无意义的短名。比如 com.example.app.LoginActivity 变成 a.a.a。这样反编译出来的代码,读起来就像天书。
我接手过一个老项目,混淆配置几乎没动。结果 APK 被扒出来,攻击者直接通过类名定位了核心支付逻辑。后来加上标准混淆,攻击成本翻了不止一倍。
| 混淆类型 | 作用 | 工具支持 |
|---|---|---|
| 重命名(Rename) | 类、方法、字段随机短名 | ProGuard、R8 |
| 移除无用代码(Shrink) | 删除未被引用的类/方法 | ProGuard、R8 |
| 优化(Optimize) | 内联、合并、简化字节码 | ProGuard、R8(R8更强) |
| 控制流混淆(Obfuscate) | 打乱代码执行路径 | 仅 R8 |
几个关键点要记住:
- 反射调用必须保留:如果你用
Class.forName()或Method.invoke(),对应的类名、方法名不能混淆,否则运行时报ClassNotFoundException。 - JNI 方法不能混淆:C/C++ 层调用的 Java 方法名要保持不变,否则 native 层找不到对应方法。
- Serializable/ Parcelable 序列化:字段名改了,反序列化就失败了——这也是经典踩坑点。
2.2 资源压缩:给 APK 减减肥
资源压缩想解决两个问题:一是把没用的资源删掉,二是把剩余资源的路径和 ID 搞乱。前者减少 APK 体积,后者增加逆向难度。
打个比方:你项目里可能残留了十几张旧版本的背景图,但 XML 里早就不引用了。资源压缩会把它们揪出来扔掉。
// build.gradle 中开启
android {
buildTypes {
release {
minifyEnabled true // 开启代码混淆
shrinkResources true // 开启资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
切记:shrinkResources 必须和 minifyEnabled 一起用,因为资源删除依赖代码混淆的引用分析。我曾经单独开 shrinkResources 没开混淆,结果把正在使用的资源也删了,打包后多处界面白屏。
资源 ID 重映射 是 R8 新增的特性(ProGuard 不支持)。它会把资源文件名和 ID 映射成短的无意义值。比如 R.layout.activity_main 变成 R.layout.a。注意:如果代码中通过 getIdentifier() 动态获取资源 ID,这些资源必须显式保留。
2.3 字符串加密:保护敏感文本
代码混淆只能混淆类和方法名,但对字符串常量完全无效。比如你的 、加密密钥、日志里的敏感信息,只要在代码里写了明文,反编译后所有人都能看到。字符串加密就是来解决这个问题的。
实现方式分两种:编译期替换 和 运行时解密。
- 编译期替换:在 Gradle 构建时通过插件(比如
DexGuard或自定义 Transform)把字符串内容加密成字节数组,然后生成解密代码。这样反编译后看到的是一堆乱码字节,不是原文。 - 运行时解密:在应用启动时,通过一个隐蔽的解密器还原字符串。解密逻辑一般内嵌在 native 层,防止被直接 Hook。
哪些字符串应该加密? 我个人认为至少这三类:
- 网络接口的 BASE_URL:尤其是内网地址或测试域名,暴露后可能被重定向。
- 对称加密的密钥:千万别写死在 Java 代码里——放在 native 层或服务端下发是更好的选择。
- 敏感日志标签:有些 app 在 release 版里忘删的日志,包含路径信息或版本号,加密后能减少风险。
2.4 三种机制如何协同工作?
你可能已经看出来了:代码混淆管「名字」,资源压缩管「体积和路径」,字符串加密管「敏感内容」。它们各司其职,但又互相依赖。
- 代码混淆 + 资源压缩:资源压缩依赖代码混淆的引用分析,所以必须同时开启。
- 字符串加密 + 代码混淆:加密后的解密类必须保持在混淆白名单外,否则解密方法本身被混淆,运行时可能找不到。
- 资源压缩 + 字符串加密:如果资源文件中包含字符串(比如
strings.xml),资源压缩只删不加密;加密需要额外工具(如AndResGuard或DexGuard)。
AndroidManifest.xml 里引用的 @string/app_name 被改成了 @string/a,导致应用名显示乱码。最后发现是 AndResGuard 的配置文件忘了加白名单。切记:所有在清单文件中使用的资源名都要保留。