Pangu-Immortal/StringFogPro
GitHub: Pangu-Immortal/StringFogPro
StringFogPro 是一款适配 AGP 8.7/9.2 的 Android Gradle 插件,通过构建期自动加密 Java/Kotlin 字符串字面量来防止反编译泄露敏感信息。
Stars: 1 | Forks: 3
## 概述
**StringFogPro** 是一款自动加密 APK / AAR 中 Java·Kotlin 字符串字面量的 **Gradle 字符串加密插件**:构建期把明文替换为「密文 + 运行时解密调用」,反编译只看到乱码,让硬编码的接口地址、密钥、Token 不再裸奔。它在经典 [StringFog](https://github.com/MegatronKing/StringFog)(Apache-2.0)基础上做**现代化超集升级**——拥抱 **AGP 8.7 / 9.2** 现代插桩 API(`AsmClassVisitorFactory` + `onVariants`),**内置 AES**(原版 v3.0.0 已删除),提供 **XOR / AES / 自定义算法**三选一,新增 **每串随机密钥**、**bytes 免 Base64 模式**、**明文→密文映射文件**,支持**按包 include/exclude + 最小串长阈值**配置。运行时是**零依赖纯 Java JAR**,一次 `./gradlew assembleRelease` 全自动完成、零侵入业务代码。**24/24 测试通过 + 真实 APK/AAR 反编译证据 + AGP 8.7/9.2 双档实测。** 适用于 Android APK/AAR/库的字符串混淆、密钥保护、反逆向加固。
## 目录
- [核心特性](#核心特性)
- [快速接入](#快速接入)
- [配置项参考](#配置项参考)
- [相对原版 StringFog 的升级](#相对原版-stringfog-的升级)
- [真实反编译证据](#真实反编译证据)
- [算法自主扩展](#算法自主扩展)
- [适配测试矩阵](#适配测试矩阵)
- [版本支持表](#版本支持表)
- [工程结构](#工程结构)
- [设计说明与已知边界](#设计说明与已知边界)
- [Roadmap(诚实标注)](#roadmap诚实标注)
- [更新日志](#更新日志)
- [致谢与 License](#致谢与-license)
## 核心特性
| 特性 | 说明 |
| --- | --- |
| **Java / Kotlin 双支持** | 工作在 ASM 字节码层,与源语言无关,两者被同样加密(附真实反编译证据) |
| **APK + AAR/JAR 加密** | Application 变体与 Library 变体走同一套 ASM 变换核心 |
| **三种算法** | 内置 **XOR+Base64(默认)** 与 **AES-128/CBC**,可注册任意自定义 `IStringFog` |
| **每串随机密钥** | 每个字符串独立随机密钥,相同明文各处密文不同,抗统计/模式分析 |
| **bytes 免 Base64** | 密文以原始 `byte[]` 字面量发射(d8 优化为 `fill-array-data`),抹除 Base64 静态特征 |
| **映射文件** | 可选输出「类#方法 · 算法 · 明文→密文」映射,便于审计与排查 |
| **细粒度配置** | 总开关 + 按包白/黑名单 + `minLength` 阈值 + 配置校验/冲突告警 |
| **完全自动化** | `onVariants` 自动注册,仅 apply 插件;仅 release 插桩,debug 保持明文 |
| **零依赖运行时** | 纯 Java JAR,仅依赖 JDK,POM 零依赖,不引入 kotlin-stdlib |
| **向后兼容** | 零配置默认路径与 v1.1.0 逐字节等价,历史工程零成本迁移 |
## 快速接入
StringFogPro 通过 JitPack 发布两个坐标:插件 `stringfog-gradle-plugin` 与运行时 `stringfog`。
### 第 1 步:声明 JitPack 仓库
`settings.gradle.kts`:
```
pluginManagement {
repositories {
maven { url = uri("https://jitpack.io") }
google(); mavenCentral(); gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
google(); mavenCentral()
}
}
```
### 第 2 步:引入插件(推荐方式 A,JitPack 最稳)
根 `build.gradle.kts` 用 buildscript classpath:
```
buildscript {
repositories {
maven { url = uri("https://jitpack.io") }
google(); mavenCentral()
}
dependencies {
classpath("com.github.Pangu-Immortal.StringFogPro:stringfog-gradle-plugin:v2.1.0")
}
}
```
### 第 3 步:应用插件 + 引入运行时 + 配置
`app/build.gradle.kts`:
```
plugins {
id("com.android.application")
// 或用方式 A:apply(plugin = "com.va.stringfog")
}
dependencies {
// 运行时解密器(零依赖纯 Java JAR)
implementation("com.github.Pangu-Immortal.StringFogPro:stringfog:v2.1.0")
}
// 可选配置;不写则零配置走默认 XOR(向后兼容 v1.1.0)
stringfog {
enabled.set(true)
algorithm.set("xor") // "xor" | "aes" | 自定义 IStringFog 全限定类名
// key.set("your-release-key") // 不设则 xor 用默认密钥、aes 用内置默认密钥
minLength.set(1)
// bytesMode.set(true) // 密文以 byte[] 发射,免 Base64
// randomKeyPerString.set(true) // 每串独立随机密钥
// mappingEnabled.set(true) // 输出 build/outputs/stringfog/ 映射文件
// fogPackages.add("com.your.app") // 只加密这些包(留空=全部)
// excludePackages.add("com.your.app.model") // 排除这些包(优先级更高)
}
```
**搞定。** 执行 `./gradlew assembleRelease`,release 产物里的字符串即被自动加密;debug 不受影响。
## 配置项参考
`stringfog { }`(`com.vapro.vae.stringfog.plugin.StringFogExtension`):
| 配置项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `enabled` | `Boolean` | `true` | 总开关;亦可 `-Pva.stringfog.enabled=false` 全局关 |
| `algorithm` | `String` | `"xor"` | `xor` / `aes` / 自定义 `IStringFog` 全限定类名 |
| `key` | `String` | 内置默认 | 密钥;未设时 xor 走 `DEFAULT_KEY`(兼容 v1.1.0),aes 走内置默认密钥 |
| `fogPackages` | `List
` | `[]` | 仅加密这些包前缀下的类(为空=全部) |
| `excludePackages` | `List` | `[]` | 排除这些包前缀下的类(优先级高于 `fogPackages`) |
| `minLength` | `Int` | `1` | 最小加密串长阈值,短于此长度保持明文 |
| `bytesMode` | `Boolean` | `false` | 密文以原始 `byte[]` 字面量发射(免 Base64) |
| `randomKeyPerString` | `Boolean` | `false` | 每个字符串独立随机密钥(抗统计分析) |
| `randomKeyLength` | `Int` | `16` | 每串随机密钥长度(字符数,仅 `randomKeyPerString` 生效) |
| `mappingEnabled` | `Boolean` | `false` | 输出映射到 `build/outputs/stringfog/stringfog-mapping-.txt` |
**配置健壮性**:`minLength` 为负 / `algorithm` 空白 / `randomKeyLength` 非正 → **抛错早失败**;`fogPackages ∩ excludePackages` 重叠、aes/自定义算法未设 key → **告警不阻塞**。
## 相对原版 StringFog 的升级
| 维度 | 原版 StringFog(5.x) | StringFogPro v2.1.0 | 实现 |
| --- | --- | --- | :---: |
| AGP 基线 | AGP 8.x / Gradle 8 | **AGP 8.7 与 9.2 双档实测** | ✅ |
| AES 算法 | v3.0.0 起**已删除** | **内置回归**(AES-128/CBC/PKCS5,随机 IV) | ✅ |
| 排除包 exclude | v1.4.0 起**已移除** | **重新提供** `excludePackages` | ✅ |
| 最小串长阈值 | 无 | 新增 `minLength` | ✅ |
| 每串随机密钥 | ✅ | `RandomKeyGenerator` 已实现 | ✅ |
| bytes 免 Base64 | ✅ | byte[] 字面量 / `fill-array-data` 已实现 | ✅ |
| mapping 映射文件 | ✅ | 构建期明文→密文映射已实现 | ✅ |
| 配置形态 | Groovy DSL 为主 | 类型化 **Kotlin DSL** + 配置校验 | ✅ |
| 运行时依赖 | 需引算法库 | **零依赖纯 Java JAR** | ✅ |
## 真实反编译证据
以下均来自本仓 `sample/` 模块 **v2.1.0 真实构建产物**的 `dexdump` / `javap` 反编译(`build-tools 36.0.0`),非虚构。debug 不插桩(release-only),故 debug DEX 即「加密前」,release DEX 即「加密后」。
**① Java 加密后(release DEX,默认 XOR,单参路径)**
```
# JavaSecrets.apiToken() —— 发布 DEX
const-string v0, "CVJjayoOcl0bDRMnFCUtay4UfxNYbAZGfHlrXjsTC1cfXw=="
invoke-static {v0}, Lcom/vapro/vae/stringfog/StringFogRuntime;.decrypt:(Ljava/lang/String;)Ljava/lang/String;
# 明文 "sk-JAVA-PLAINTEXT-..." 在 release DEX 计数=0(消失);debug=1
```
**② AES 模式(`-Psf.algo=aes -Psf.key=my-release-key-2026`,三参路径)**
```
const-string v0, "my-release-key-2026"
const-string v1, "aes"
const-string v2, "DkySB3hkRyzW6kHRx6VGwjISWgTY+L+oaRDZ1qGGh7e/R0V9IJoX9Ny/xEOoRt5ik7jOjFJWwwOhz8hcEr+FJQ=="
invoke-static {v2, v0, v1}, L…/StringFogRuntime;.decrypt:(3×String)String
```
**③ 每串随机密钥(`-Psf.randomKey=true`)** —— 每串一个随机 16 位密钥(6 串实测 6 个互异密钥):
```
const-string v0, "xni1fX647FTX3nn9" # 该串专属随机密钥
const-string v1, "xor"
const-string v2, "CwVEeycOdxlnChURfTorYSxDWANVbAMCAH5taFIMDV0dCA=="
invoke-static {v2, v0, v1}, L…/StringFogRuntime;.decrypt:(3×String)String
```
**④ bytes 模式(`-Psf.bytes=true`)** —— 无 Base64 文本,密文以 `fill-array-data` 存储:
```
new-array v0, v0, [B
fill-array-data v0, 0000000c
invoke-static {v0}, L…/StringFogRuntime;.decrypt:([B)Ljava/lang/String;
```
**⑤ AAR 库端到端(`sample/lib` → `classes.jar` → `javap -c`)** —— 库字符串同样被加密:
```
# LibJavaSecrets.libApiToken() —— AAR classes.jar
0: ldc #35 // String CVJjbSIaHjoKFxNDCj0pejRtC3k/dVIROWwzDDkVDVVLC30VXm4=
2: invokestatic #33 // Method .../StringFogRuntime.decrypt:(String)String
# classes.jar 内 4 条库明文 grep 计数均=0
```
## 算法自主扩展
实现 `IStringFog`(构建期加密与运行时解密的唯一契约),构建期用 DSL 指定全限定类名、运行时以同名 id 注册。
```
// 1) 实现算法(放在 buildscript classpath 与 App 都能看到的位置)
package com.your.app.crypto;
import com.vapro.vae.stringfog.IStringFog;
public final class MyFog implements IStringFog {
@Override public byte[] encrypt(byte[] data, byte[] key) { return transform(data, key); }
@Override public byte[] decrypt(byte[] data, byte[] key) { return transform(data, key); }
private byte[] transform(byte[] d, byte[] k) { /* ... */ return d; }
}
```
```
// 2) 构建期指定(需 public 无参构造,构建期反射构造)
stringfog { algorithm.set("com.your.app.crypto.MyFog") }
```
```
// 3) 运行时注册(App 早期,如 Application.onCreate)
StringFogRuntime.register("com.your.app.crypto.MyFog", new com.your.app.crypto.MyFog());
```
内置 `xor` / `aes` 已默认注册,无需手动 register。
## 适配测试矩阵
**`./gradlew test` 汇总:24 / 24 通过(0 失败 0 错误)** —— `stringfog` 11 + `stringfog-gradle-plugin` 13。
| 维度 | 结果 | 证据 |
| --- | :---: | --- |
| **AGP 9.2.1 + Gradle 9.6 + JDK 21 + compileSdk 36** | ✅ 实测 | `sample/` 真实 APK+AAR 反编译 |
| **AGP 8.7.3 + Gradle 8.13 + JDK 21 + compileSdk 35** | ✅ 实测 | `sample-agp8/` 真实 APK:release 明文=0,debug=1 |
| Java @ APK / Kotlin @ APK | ✅ | `sample/app` release DEX 明文计数=0 |
| Java @ AAR / Kotlin @ AAR | ✅ | `sample/lib` `classes.jar` javap 反编译,明文=0 |
| XOR / AES / 每串随机密钥 / bytes 往返 | ✅ | `StringFogRuntimeTest` + `StringFogTransformTest` |
| v1.1.0 逐字节向后兼容 | ✅ | `backwardCompatWithV110` |
| AGP 7.2 ~ 8.6 | ⚠️ 未逐版实测 | API 自 AGP 7.2 稳定,理论兼容 |
| JDK 17(下限) | ⚠️ 未实测 | 产物 target=17,本环境以 JDK 21 构建 |
## 版本支持表
| 组件 | 版本 | 说明 |
| --- | --- | --- |
| AGP | **8.7.3 与 9.2.1(双档实测)** | `AsmClassVisitorFactory`(AGP 7.2+ 提供);7.2~8.6 理论兼容未逐版实测 |
| Gradle | **8.13 与 9.6.0** | 主构建锁 9.6.0;`sample-agp8` 锁 8.13 |
| Kotlin | 随 Gradle `kotlin-dsl` 内嵌 | 插件源码 100% Kotlin |
| JDK | 构建/测试 **JDK 21**;产物字节码 **Java 17(下限)** |版本字节码向上兼容 |
| ASM | **9.9** | 由 AGP 内置提供 |
| minSdk | **26+** | 运行时用 `java.util.Base64`(API 26 起);sample 取 28 |
## 工程结构
```
StringFogPro/
├── stringfog/ # 运行时 + 算法库(纯 Java,零依赖,JitPack 发布)
│ └── .../stringfog/
│ ├── IStringFog.java # 加解密统一契约
│ ├── IKeyGenerator.java # 每串密钥生成器契约
│ ├── RandomKeyGenerator.java# 随机每串密钥实现
│ ├── XorFog.java # 默认 XOR 循环密钥(兼容 v1.1.0)
│ ├── AesFog.java # AES-128/CBC/PKCS5(随机 IV 前置)
│ └── StringFogRuntime.java # 运行时解密调度器(四入口 + 注册表)
├── stringfog-gradle-plugin/ # AGP 插桩插件(100% Kotlin,JitPack 发布)
│ └── .../plugin/
│ ├── StringFogPlugin.kt # onVariants release 注册 + 配置校验
│ ├── StringFogExtension.kt # stringfog { } DSL
│ ├── StringFogFactory.kt # AsmClassVisitorFactory
│ └── core/ # 纯 ASM 变换核心(可独立单测)
│ ├── FogConfigResolver.kt
│ ├── PackageFilter.kt
│ ├── MappingWriter.kt
│ └── StringFogMethodVisitor.kt
├── sample/ # 演示多模块(:app APK + :lib AAR)
└── sample-agp8/ # 多 AGP 适配第二档(AGP 8.7.3 + Gradle 8.13)
```
## 设计说明与已知边界
- **运行时为何是 Java 而非 Kotlin?** 运行时 JAR 会被打进每个消费方 APK,目标是**零依赖极小**。用 Kotlin 会强制引入 `kotlin-stdlib`(约 1.5MB+),破坏零依赖定位。加密在 ASM 字节码层,Kotlin 业务类同样被加密。
- **密钥并非密码学机密。** 密钥(含每串随机密钥)随字节码嵌入 APK。目标是**抵御 `strings`/grep 明文扫描、抬高逆向成本**,而非提供不可破解的加密。
- **编译期常量(`const val` / `static final String`)不被加密。** 它们以 `ConstantValue` 属性内联,非方法体 `LDC` 指令;敏感串请用方法返回值或非 const 字段。
- **非常量拼接不误伤。** 仅改 `LDC` String 常量;Java 9+ 的 `invokedynamic makeConcatWithConstants` 常量在 BSM 引导参数中、非 LDC,不触碰。
- **超长串跳过。** 超阈值(Base64 45000 / bytes 8000 UTF-8 字节)保持明文,避免常量池 65535 上限,远超真实密钥/URL 长度。
- **作用域 `PROJECT`。** 仅加密本模块源码,不加密依赖(避免二次加密 / 加密 AndroidX)。
## Roadmap(诚实标注)
- [ ] **`plugins {}` 免接线声明式(Gradle Plugin Portal marker)** —— 需外部发布账号,本项目暂无;当前走 buildscript classpath 或 `resolutionStrategy` 映射。
- [ ] **AGP 7.2 ~ 8.6 逐版实测** —— API 层理论兼容,已实测 8.7.3 与 9.2.1 两档。
- [ ] **JDK 17 下限实测** —— 产物面向 Java 17,本环境以 JDK 21 构建/测试。
- [ ] **R8/混淆共存实测** —— sample 关闭 R8 聚焦插桩;插桩发生在 R8 前,理论无冲突。
## 更新日志
### v2.1.0(生产级打磨)
- **补齐能力**:每串随机密钥、bytes 免 Base64、明文→密文映射文件、AAR 库端到端反编译证据。
- **运行时四入口**:Base64/bytes × 单参/三参 的 `decrypt` 重载。
- **代码精修**:`shouldFog` 接口默认实现;ASM 边界防护;`AesFog` 静态 `SecureRandom`;`PackageFilter` 可单测;线程安全审计。
- **配置健壮性**:`minLength`/`algorithm`/`randomKeyLength` 校验早失败;重叠告警。
- **适配矩阵**:24/24 通过;AGP 8.7.3+Gradle 8.13 与 AGP 9.2.1+Gradle 9.6 双档;Java/Kotlin × APK/AAR 真实反编译。
### v2.0.0(超集升级)
- 多模块化;统一 `IStringFog` 契约;内置 AES-128/CBC;算法自主扩展;细粒度配置。
### v1.1.0
- 单模块纯 Java 运行时库:`StringFogRuntime.decrypt(String)`(XOR + Base64)。
## 致谢与 License
本项目基于 [MegatronKing/StringFog](https://github.com/MegatronKing/StringFog)(Apache-2.0)思想改写并做现代化超集升级,向原作者致谢。
本项目采用 **Apache-2.0** 协议,**允许商用**。详见 [LICENSE](LICENSE)。
标签:Android开发, Gradle插件, JS文件枚举, 代码保护, 代码混淆, 字符串加密, 目录枚举, 移动安全