How to obfuscate a package with Gradle, Maven and ProGuard

By , last updated June 26, 2019

The way of packaging code changes all the time. In this article we will show you how to pack Java code in a JAR file with Gradle and Maven plugins and give an Android ProGuard obfuscation example with ProGuard library.

We are using IntelliJ community edition to write server side java code for our Android Application. The code base is not large, so we make a simple API and obfuscate the implementation. The project structure will look like this:

-src/main/java
--api
--internal  
-src/main/resources

-src/test/java
-src/test/resources

Gradle

Gradle is a packaging library that comes preinstalled with many development IDEs like Android Studio. It is a Groovy-based domain specific language where all build specific settings are defined in gradle.build and gradle.settings files.

You tell Gradle to build things by writing a set of tasks. An important thing to understand here is that ALL tasks that are provided within one .build file will be executed no matter what. You can control the order of execution, but you cannot skip any one. If you need different tasks for different packages – you need to write different .build files.

Here is an example of a simple Gradle task that creates an archive with java files:

apply plugin: 'idea'
apply plugin: 'java'

group 'com.verifone'
version '0.0.2'

def javaName = archivesBaseName + '-java'

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }
    }
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.13'
    runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5'
    runtime 'org.apache.logging.log4j:log4j-api:2.5'
    runtime 'org.apache.logging.log4j:log4j-core:2.5'

    // test
    testCompile 'junit:junit:4.11'
}


jar {
    baseName = javaName
    manifest {
        attributes "Implementation-Title": javaName
        attributes "Implementation-Version": version
    }
}

Maven plugin

Within Gradle you can apply a Maven plugin in order to install the resulting JAR file into maven repository. Once the file is installed, it can be used in all other applications. This is done with a simple syntax:

apply plugin: 'maven'

Maven provides a default task install that moves the packaged java file into maven repository. Here is an example of simple set of tasks to package and move files into local maven repository:

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'maven'

group 'com.mycompany'
version '0.0.1'
def javaName = archivesBaseName + '-java'

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.13'
    runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5'
    runtime 'org.apache.logging.log4j:log4j-api:2.5'
    runtime 'org.apache.logging.log4j:log4j-core:2.5'

    // test
    testCompile 'junit:junit:4.11'
}


jar {
    baseName = javaName
    manifest {
        attributes "Implementation-Title": javaName
        attributes "Implementation-Version": version
    }

}

buildscript {
    repositories {
        mavenCentral()
    }
}

install {
    repositories.mavenInstaller {
        addFilter(javaName) { artifact, file -> artifact.name.endsWith(javaName) }
        pom(javaName).whenConfigured {
            p ->
                p.dependencies = p.dependencies.findAll {
                    dep -> dep.artifactId != "junit"
                }
        }
    }
}

ProGuard

Proguard is a library to make Java code better – optimized, shrinked, obfuscated or preverified. ProGuard should be imported within a gradle.build file:

import proguard.gradle.ProGuardTask

With ProGuard we can obfuscate (change all class names to something unreadable) our internal implementation files. This is useful when we don’t want to spend time building separate API packages, but place everything in one project. In this case, when we share our code with other parties they can come to use our internal implementation classes instead of intended interfaces. We change the names of internal classes to “a,b,c,d…” and make it more difficult for people to use our internal code.

The process of obfuscating with gradle is as follows:

  1. Package a full JAR file that includes all files
  2. Obfuscate files in this full package and output everything into another JAR-file
  3. Move the obfuscated JAR file into maven repository

Per default, ProGuard library obfuscates all files in the given JAR package. We need to obfuscate internal package only. For this we create a ProGuard settings file proguard.txt where we explicitly specify what to obfuscate and optimize and what not to obfuscate:

-keepparameternames
-renamesourcefileattribute SourceFile
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-keep class com.myfiles.api.** { *; }
-keep class com.myfiles.internal.json.** { *; }
-keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean); }
-optimizations !code/allocation/variable

-keep class javax.** { *; }
-keep class java.** { *; }
-keep class org.** { *; }

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

Our proguard.txt file defines configuration or exceptions from default obfuscation behavior:

  1. We need to keep API classes unobfuscated in order to our customers to use the API.
  2. If you are using mapping frameworks like json that use reflection than you need to keep all such classes unobfuscated as well.
  3. Remember to keep all javax.*, java.* and org.* classes as obfuscation of these objects will corrupt the program at runtime.
  4. Some methods often need to be kept, especially if you use constructions like MyApi.getSomething(). If the method name is obfuscated it will result in NullPointerException at runtime.

Building jars with ProGuard happens in a task “proguard” where you specify:

  • injar – JAR packages that need to be obfuscated.
  • outjar – the resulting JAR package.
  • libraryjars – what library JARS to include.
  • printmapping – helper obfuscation mapping file that helps to analyze the obscured data later.

Here is a full example of gradle.build configuration file that uses our custom proguard.txt:

import proguard.gradle.ProGuardTask

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'maven'

group 'com.mycompany'
version '0.0.1'

def javaName = archivesBaseName + '-java'
def fullJavaName = archivesBaseName + '-full'

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
	compile 'com.google.code.gson:gson:2.3.1'
    compile 'org.slf4j:slf4j-api:1.7.13'
    runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5'
    runtime 'org.apache.logging.log4j:log4j-api:2.5'
    runtime 'org.apache.logging.log4j:log4j-core:2.5'

    // test
    testCompile 'junit:junit:4.11'
}


jar {
    baseName = javaName
    manifest {
        attributes "Implementation-Title": javaName
        attributes "Implementation-Version": version
    }

}

task fullJavaJar(type: Jar) {
    baseName = fullJavaName
    manifest {
        attributes "Implementation-Title": baseName
        attributes "Implementation-Version": version
    }
    from sourceSets.main.output
    artifacts {
        archives fullJavaJar
    }
}

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'net.sf.proguard:proguard-gradle:5.2.1'
    }
}


task proguard(type: ProGuardTask, dependsOn: 'obfuscatedJar') {
    configuration 'proguard.txt'

    injars "$buildDir/libs/${fullJavaName}-${version}.jar"
    outjars "$buildDir/libs/${javaName}-${version}.jar"

    libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
    libraryjars configurations.compile.find { it.name.startsWith("gson") }
    libraryjars configurations.compile.find { it.name.startsWith("slf4j-api") }
}

task obfuscate(dependsOn: tasks.withType(ProGuardTask)) << {}

install.dependsOn(obfuscate)

install {
    repositories.mavenInstaller {
        addFilter(javaName) { artifact, file -> artifact.name.endsWith(javaName) }
        pom(javaName).whenConfigured {
            p ->
                p.dependencies = p.dependencies.findAll {
                    dep -> dep.artifactId != "junit"
                }
        }
    }
}