StringFogPro 是一款自动加密 APK / AAR 中 Java·Kotlin 字符串字面量的 Gradle 字符串加密插件:构建期把明文替换为「密文 + 运行时解密调用」,反编译只看到乱码,让硬编码的接口地址、密钥、Token 不再裸奔。它在经典 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 的升级
- 真实反编译证据
- 算法自主扩展
- 适配测试矩阵
- 版本支持表
- 工程结构
- 设计说明与已知边界
- Roadmap(诚实标注)
- 更新日志
- 致谢与 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。
settings.gradle.kts:
pluginManagement {
repositories {
maven { url = uri("https://jitpack.io") }
google(); mavenCentral(); gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
google(); mavenCentral()
}
}根 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")
}
}方式 B(声明式
plugins {}):JitPack 服务 module 坐标但不服务插件 marker,需在pluginManagement加resolutionStrategy映射:pluginManagement { resolutionStrategy { eachPlugin { if (requested.id.id == "com.va.stringfog") { useModule("com.github.Pangu-Immortal.StringFogPro:stringfog-gradle-plugin:${requested.version}") } } } }之后即可
plugins { id("com.va.stringfog") version "v2.1.0" }(已实测该 module 坐标可解析装载)。
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 不受影响。
全局临时关闭:
./gradlew assembleRelease -Pva.stringfog.enabled=false
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<String> |
[] |
仅加密这些包前缀下的类(为空=全部) |
excludePackages |
List<String> |
[] |
排除这些包前缀下的类(优先级高于 fogPackages) |
minLength |
Int |
1 |
最小加密串长阈值,短于此长度保持明文 |
bytesMode |
Boolean |
false |
密文以原始 byte[] 字面量发射(免 Base64) |
randomKeyPerString |
Boolean |
false |
每个字符串独立随机密钥(抗统计分析) |
randomKeyLength |
Int |
16 |
每串随机密钥长度(字符数,仅 randomKeyPerString 生效) |
mappingEnabled |
Boolean |
false |
输出映射到 build/outputs/stringfog/stringfog-mapping-<variant>.txt |
配置健壮性:minLength 为负 / algorithm 空白 / randomKeyLength 非正 → 抛错早失败;fogPackages ∩ excludePackages 重叠、aes/自定义算法未设 key → 告警不阻塞。
| 维度 | 原版 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 | ✅ |
诚实声明:与原版的真正差距收敛到「
plugins {}免接线声明式解析」——它需把插件 marker 发布到 Gradle Plugin Portal(需外部发布账号,本项目暂无)。JitPack 下用 buildscript classpath 或resolutionStrategy映射即可(见快速接入)。bytes 模式「省体积」诚实说明:本仓 6 条短串样例整包 DEX 差异仅 +8 字节(可忽略),故 bytes 模式首要价值是抹除 Base64 文本特征 + 免运行时 Base64 解码,而非对短串省体积。
以下均来自本仓 sample/ 模块 v2.1.0 真实构建产物的 dexdump / javap 反编译(build-tools 36.0.0),非虚构。debug 不插桩(release-only),故 debug DEX 即「加密前」,release DEX 即「加密后」。
① Java 加密后(release DEX,默认 XOR,单参路径)
# JavaSecrets.apiToken() —— release 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
复现:根
./gradlew publishToMavenLocal→./gradlew -p sample :app:assembleDebug :app:assembleRelease :lib:assembleRelease(各模式加-Psf.*)→dexdump -d(APK)/javap -c(AAR)对比即得。
实现 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 单测/集成)、真实 APK/AAR 反编译、AGP 8.7 与 9.2 双档真实构建。声明=测试,未测的诚实标注。
./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 字段。 - 非常量拼接不误伤。 仅改
LDCString 常量;Java 9+ 的invokedynamic makeConcatWithConstants常量在 BSM 引导参数中、非 LDC,不触碰。 - 超长串跳过。 超阈值(Base64 45000 / bytes 8000 UTF-8 字节)保持明文,避免常量池 65535 上限,远超真实密钥/URL 长度。
- 作用域
PROJECT。 仅加密本模块源码,不加密依赖(避免二次加密 / 加密 AndroidX)。
-
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 已补齐的原 Roadmap 项:每串随机密钥、bytes 免 Base64、mapping 映射文件、AAR 库端到端反编译证据——均附实现位与测试/反编译证据。
- 补齐能力:每串随机密钥、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 真实反编译。
- 多模块化;统一
IStringFog契约;内置 AES-128/CBC;算法自主扩展;细粒度配置。
- 单模块纯 Java 运行时库:
StringFogRuntime.decrypt(String)(XOR + Base64)。
本项目基于 MegatronKing/StringFog(Apache-2.0)思想改写并做现代化超集升级,向原作者致谢。
本项目采用 Apache-2.0 协议,允许商用。详见 LICENSE。
关于作者
主业大模型算法 / AI / 端侧方向(Agentic · LangGraph · A2A · MCP · ADK · GraphRAG · 端侧离线多模态 · 车载 · 世界模型);ROM / 逆向是我长期钻研的技术兴趣。
欢迎交流合作 · 📮 yugu88@126.com · GitHub @Pangu-Immortal