4. 敏感数据字段的混淆保护策略

4.1 原理:为什么不能混淆金额、订单号、签名串

ProGuard 默认会将类成员(字段、方法)重命名为简短无意义的标识符,如 a、b。对于支付核心代码中的金额、订单号、签名串等字段,混淆后会导致以下问题:

  • 序列化/反序列化失败:很多场景使用 Gson、FastJson 或 Kotlin serialization 时,字段名通过反射或命名策略映射,混淆后的名称与通信协议或序列化名称不匹配。
  • 签名校验失效:签名串字段名被混淆后,服务端或客户端根据固定字段名生成签名时无法正确提取值。
  • 动态访问受阻:如果代码通过反射 field.setAccessible(true) 等方式动态读写金额字段,混淆后字段名已变化,导致 NoSuchFieldException。
  • 日志/监控混淆:调试日志中打印的字段名变为 a/b,难以定位问题。

因此,对这些字段必须使用 Keep 策略——即保留原始字段名称,禁止混淆重命名。

关键原则:混淆不是万能的,支付敏感字段需要保留原名以保证运行时正确性。不要为了“更安全”而错误地让所有字段都混淆。

4.2 保护方式对比:@Keep 注解 vs keep 规则

维度@Keep 注解keep 规则(proguard-rules.pro)
适用范围只对标注的 单个字段/类/方法 生效可匹配 包、类、字段名模式,批量生效
侵入性需要在源码中显式添加注解完全在配置文件中定义,不修改源码
可维护性注解紧贴字段定义,易于检索所有规则集中在 proguard-rules.pro,适合全局管理
第三方库兼容无法修改第三方库源码时不可用可对第三方库的字段编写 keep 规则
推荐场景自己维护的实体类、支付接口层接入的 SDK、数据模型、序列化框架

4.3 实现流程与代码/配置示例

4.3.1 流程步骤

  1. 识别敏感字段:在支付核心代码中,检索所有出现 amount、orderNo、sign、transactionId、merchantId 等字段的类。
  2. 选择保护策略:对自有代码使用 @Keep 注解;对第三方依赖或需要通配命中的字段使用 keep 规则。
  3. 编写配置:在 proguard-rules.pro 中添加 -keep 规则,或使用 @Keep 注解。
  4. 验证:通过 mapping.txt 检查字段是否被重命名。

4.3.2 使用 @Keep 注解(Java / Kotlin)

// Java 示例:支付请求实体
import android.support.annotation.Keep;

public class PaymentRequest {
    @Keep
    public String orderNo;          // 订单号(保留)
    @Keep
    public double amount;           // 金额(保留)
    @Keep
    public String sign;             // 签名串(保留)
    public String extraInfo;        // 可混淆
}
// Kotlin 示例(需引入 @Keep 注解)
import androidx.annotation.Keep

data class PaymentResponse(
    @Keep val transactionId: String,  // 保留
    @Keep val status: String,         // 保留
    val rawData: String               // 可混淆
)

注意:@Keep 注解在 Android 中由 androidx.annotation:annotation 或 android.support.annotation 提供,且在 ProGuard 中默认会识别。无需额外配置即可生效。

4.3.3 使用 keep 规则(proguard-rules.pro)

# 保留 PaymentRequest 类的所有字段名(含 keep 已在注解中,也可直接写)
-keep class com.pay.sdk.model.PaymentRequest {
    *;   # 保留该类所有成员
}

# 更精确:只保留特定字段名
-keep class com.pay.sdk.model.PaymentResponse {
    public java.lang.String transactionId;
    public java.lang.String status;
}

# 保留包下所有类的所有字段(谨慎使用)
-keep class com.pay.sdk.model.** { *; }

规则说明:

  • -keep class 类名 { 字段签名; }:保留指定字段不被混淆名称。
  • * 通配符表示所有成员(字段、方法)都保留原名。
  • 可以使用 <fields> 保留所有字段:-keep class 类名 { <fields>; }。
最佳实践:优先使用 @Keep 注解来精细化控制单个字段,避免通配 * 导致保护过度、增大包体积。但注意 @Keep 只能保护字段名不被混淆,不能阻止代码移除或内联,如需完全保留成员则需结合 -keep。

4.4 调试:验证敏感字段是否被保留

  1. 执行 Release 构建(通常会自动生成 mapping.txt):
    ./gradlew assembleRelease
  2. 解压 APK 或查看 build/outputs/mapping/release/mapping.txt。
  3. 搜索敏感字段名(如 orderNo、amount),如果该字段出现在 mapping.txt 的左侧(原名 → 混淆名映射)中,说明未被保留;如果只在右侧(混淆后名)中出现,或者根本没有出现(表示被删除),则保留失败。
# mapping.txt 示例(正确保留的字段不会出现在这里)
# 如果出现类似:com.pay.sdk.model.PaymentRequest -> com.pay.sdk.model.a:
#     java.lang.String amount -> a   // 表示 amount 被重命名,错误!
# 应该没有任何 amount 字段的重命名记录,或者记录为:
#     java.lang.String amount -> amount  // 表示保留原名

可靠的方法:在代码中反射读取 PaymentRequest.class.getDeclaredFields() 并打印名称,在 Release 版本中验证。

常见陷阱:@Keep 在某些旧的 ProGuard 版本中可能不被识别。确保项目中引入了正确的 androidx.annotation:annotation 依赖,并且 ProGuard 配置文件不排除该注解。如果仍失效,请改用显式 -keep 规则。

4.5 部署注意事项

  • 多 flavor 场景:每个 Build Type 或 Product Flavor 都可以有独立的 ProGuard 配置(如 src/release/proguard-rules.pro),务必确认 release 的规则包含了 keep 字段。
  • 与资源混淆(AndResGuard)共存:AndResGuard 只处理资源,不影响字段混淆,两者无冲突。
  • 签名串字段:如果你的签名串是拼接后的字符串,通常会作为普通字段存在,请务必 keep。如果签名串是通过函数动态计算的,则只需 keep 该函数。

4.6 常见错误及解决

错误现象可能原因解决方案
发布后支付签名失败签名串字段 sign 被混淆名覆盖,服务端无法解析在 keep 规则中明确保留 sign 字段
Gson 反序列化得到 null金额字段 amount 被重命名,Gson 按 a 找不到映射添加 -keepclassmembers class 类名 { double amount; }
反射报 NoSuchField通过字段名字符串反射获取,混淆后名称变化对反射字段使用 -keepclassmembers 并指定原始名称
@Keep 注解不生效ProGuard 版本低或未引入 android.support.annotation升级 ProGuard 至 6.0+,确保 proguard-android-optimize.txt 未移除 Keep

4.7 性能与稳定性优化

  • 避免过度 Keep:不要对整个支付模块使用 -keep class com.pay.** { *; },这会导致大量代码无法优化,增加包体积(10%~30%)。
  • 字段级粒度优先:只对确需保留的字段加上 @Keep 或精确规则,其他字段仍可混淆。
  • 开启 ProGuard 优化:在 proguard-rules.pro 中不要禁用 -optimizationpasses 5,让非敏感部分获得压缩收益。
  • Kotlin 数据类:数据类的 componentN() 方法依赖于字段名,如果字段被混淆可能破坏解构。如需保持解构,应保留所有字段名或使用 @Keep 注解在类级别。
稳定性建议:在 CI/CD 流程中增加自动化测试——构建 Release 后通过反射检查所有预期保留的字段名是否未被混淆。如果发现字段名被重命名,立即构建失败并提示。

4.8 敏感字段保留流程概览(SVG)

敏感字段 Keep 决策流程 识别敏感字段 amount, orderNo, sign 自有代码? (可加注解) 是 @Keep 注解 直接标在字段上 否 keep 规则文件 proguard-rules.pro 编译 Release 生成 mapping.txt 验证保留 mapping检查/反射 ⚠️ 混淆前后字段名一致才算保留成功