Java代码保护方法之一:ProGuard

老丁谈职场 2025-01-31 14:08:35

ProGuard是一个开源的Java程序代码压缩、优化和混淆工具,是目前Java代码保护最常用的工具。

“瘦身高手”+“优化小能手”+“混乱大师”。

1、压缩(瘦身高手):检查并删除没有被使用的类、字段、方法和属性,从而减小代码库的大小。提高应用的启动速度和降低内存占用。

2、优化(优化小能手):分析代码,并进行一些基本的优化,如内联方法、去除死代码、常量表达式求值、删除不必要的字段存取和方法调用等。能在一定程度上提高代码的执行效率。

3、混淆(混乱大师):将类名、方法名和字段名重命名为难以理解的字母和数字序列,增加反编译的难度,从而保护应用程序不被恶意篡改或进行逆向工程。

对于大部分Java开发者来说,使用最多的是ProGuard的混淆功能。

以Java后端为例,来具体说明ProGuard的使用方法和注意事项。

一、准备工作

jdk1.8

安装jdk1.8

设置环境变量JAVA_HOME和PATH

maven:3.9.3

安装maven

设置环境变量MAVEN_HOME和PATH

二:配置项详解

ProGuard提供了GUI和Maven插件两种使用方法。

对于开发者而言,Maven方式更为方便。通常的配置如下:

maven 配置:

<plugin><groupId>com.github.wvengen</groupId><artifactId>proguard-maven-plugin</artifactId><version>2.0.14</version><executions><execution><phase>package</phase><goals><goal>proguard</goal></goals></execution></executions><configuration><obfuscate>true</obfuscate><proguardVersion>6.2.2</proguardVersion><proguardInclude>${project.basedir}/proguard.cfg</proguardInclude><injar>${project.build.finalName}.jar</injar><outjar>${project.build.finalName}.jar</outjar><libs><lib>${java.home}/lib/rt.jar</lib></libs></configuration><dependencies><dependency><groupId>net.sf.proguard</groupId><artifactId>proguard-base</artifactId><version>6.2.2</version><scope>runtime</scope></dependency></dependencies></plugin>

proguard.cfg文件:

####以下是推荐的固定配置项#指定Java的版本-target 1.8#proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等-dontshrink#是否关闭字节码级别的优化,如果不开启则设置如下配置 不做优化(变更代码实现逻辑)-dontoptimize#混淆时不生成大小写混合的类名,默认是可以大小写混合-dontusemixedcaseclassnames#不会因为警告而中断-dontwarn# 对于类成员的命名的混淆采取唯一策略-useuniqueclassmembernames#混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-adaptclassstrings#保持目录结构-keepdirectories#对异常、注解信息予以保留-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod,Qualifier#保留枚举成员及方法-keepclassmembers enum * { *; }#保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数#不参与混淆的类参数也不混淆,controller如果参数也混淆会导致传参映射不上-keepparameternames# 不混淆所有类,保存原始定义的注释--keepclassmembers * {@org.springframework.context.annotation.Bean *;@javax.annotation.Resource *;@org.springframework.beans.factory.annotation.Autowired *;@org.springframework.beans.factory.annotation.Value *;@org.springframework.stereotype.Service *;@org.springframework.stereotype.Component *;@org.springframework.context.annotation.Configuration *;@org.springframework.stereotype.Repository *;@org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration *;@org.springframework.boot.context.properties.ConfigurationProperties *;@org.springframework.web.bind.annotation.RestController *;@org.springframework.beans.factory.annotation.Qualifier *;@io.swagger.annotations.ApiParam *;@org.springframework.validation.annotation.Validated *;@io.swagger.annotations.ApiModelProperty *;@javax.validation.constraints.NotNull *;@javax.validation.constraints.Size *;@javax.validation.constraints.NotBlank *;@javax.validation.constraints.Pattern *;}-keep org.springframework.** {*;}-keep public ch.qos.logback.**{*;}-keep com.fasterxml.jackson.** { *; }#忽略warn消息-ignorewarnings#打印配置信息-printconfiguration######以下这些都需要根据具体项目而更改#入口程序类不能混淆,混淆会导致springboot启动不了-keep public com.dxc.project1.AppStart { *;}#mybatis的mapper/实体类不混淆,否则会导致xml配置的mapper找不到 ( 保持该目录下所有类及其成员不被混淆)-keepcom.dxc.project1.mybatis.mapper.** {*;}-keepcom.dxc.project1.mybatis.domain.** {*;}# controller 层映射前台参数的类、后端返回的 bean 属性类等,不能混淆类的成员属性(如变成 string a;)-keepclassmembers com.dxc.project1.**.*request {*;}-keepclassmembers com.dxc.project1.**.*response {*;}-keepclassmembers com.dxc.project1.**.*vo {*;}#还有一些配置类和Bean不能混淆 比如logPointCut prefix 这些 @Pointcut###配置文件:@ConfigurationProperties(prefix = "redisson")-keepcom.dxc.project1.aop.aspectj.** {*;}#保留Serializable序列化的类不被混淆-keepclassmembers * implements java.io.Serializable {*;}

参数选项说明:

保留选项说明:

注意选项的命名规律:-keep*用于防止目标被移除或者重命名、-keep*names则仅仅用于防止重命名。

压缩选项:

优化选项:

混淆选项:

预校验选项:

大部分参数不用配置或者使用默认值即可,我建议压缩和优化开关关闭,ProGuard只用来做代码混淆,否则可能会出现一些莫名其妙的问题。

三:多模块

实际的项目中,需要混淆的一般不会是单模块,例如项目project1是主项目,project1依赖project2模块。

<dependency><groupId>com.dxc</groupId><artifactId>project2</artifactId><version>1.0-SNAPSHOT</version></dependency>

需要同时混淆project1和project2,此时的maven配置:

<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.3.0</version><configuration><!-- 打包生成的包不拼接xml里的id 为后缀 --><appendAssemblyId>false</appendAssemblyId><!--打包文件路径--><descriptors><descriptor>${project.basedir}/assembly.xml</descriptor></descriptors></configuration><executions><execution><id>assembly-package</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>com.github.wvengen</groupId><artifactId>proguard-maven-plugin</artifactId><version>2.0.14</version><executions><execution><phase>package</phase><goals><goal>proguard</goal></goals></execution></executions><configuration><obfuscate>true</obfuscate><proguardVersion>6.2.2</proguardVersion><proguardInclude>${project.basedir}/proguard.cfg</proguardInclude><injar>${project.build.finalName}.jar</injar><outjar>${project.build.finalName}.jar</outjar><libs><lib>${java.home}/lib/rt.jar</lib></libs></configuration><dependencies><dependency><groupId>net.sf.proguard</groupId><artifactId>proguard-base</artifactId><version>6.2.2</version><scope>runtime</scope></dependency></dependencies></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.3.RELEASE</version><configuration><mainClass>com.dxc.project1.AppStart</mainClass><fork>true</fork><excludeGroupIds>com.dxc</excludeGroupIds></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins>

assembly.xml:

<?xml version="1.0" encoding="UTF-8"?><assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"><!--项目标识,设置的话,生成后的zip文件会加上此后缀--><id>${project.version}</id><!--打包格式--><formats><format>jar</format></formats><!--压缩包下是否生成和项目名相同的根目录--><includeBaseDirectory>false</includeBaseDirectory><fileSets><fileSet><directory>${project.build.directory}/classes</directory><outputDirectory>/</outputDirectory></fileSet></fileSets><dependencySets><!-- 把依赖的项目其他模块代码放到jar包里,方便对其他模块代码 --><dependencySet><includes><include>com.dxc:*</include></includes><unpack>true</unpack></dependencySet></dependencySets></assembly>

混淆后的结果:

总结

ProGuard开箱即用,虽便捷但配置项颇为繁琐,配置完成后还需经过严格的测试流程以确保无误。

此外,代码编写也需遵循一定规范,这无疑增加了开发者的约束,不能随心所欲编程。

尽管这是当前最为普及的Java源码保护手段,但其局限性显而易见:它并未触及代码的逻辑核心与整体架构,仅仅是为破解者增设了一道屏障,提高了些许破解成本。

因此,我们在采用此方法时,应理性看待其保护效果,寻求更为全面和根本的代码保护策略。

0 阅读:0
老丁谈职场

老丁谈职场

10年工作经历 一起聊聊职场那些事