Play 2.6.x project obfuscation with Proguard 6.0.2

I downloaded (https://www.playframework.com/download#starters) a simple example of a Play project called “Play Java Starter Example” (Play 2.6.x Starter Projects) and I would like to obscure it with Proguard6.0.2.

Instead of including the obfuscation as a build part (with sbt-proguard), I opened the zip file generated by activator dist, obfuscated the jar and returned it back into the lib folder with the same name. Here is how to do it.

Proguard can run on its own. You simply download the tar file from sourceforge and run the jar with a configuration file like:

java -jar /path/to/proguard/lib/proguard.jar @myConfig.pro

Now for the configuration file, You need to specify:

  • injar - the jar being obfuscated - After unzipping the zip file created by the dist, cd into the lib file and found the jar names YOUR_PROJECT.VERSION-sans-externalized.jar this is the jar you need to obfuscate.
  • outjar - the path for the obfuscated jar (output). At the end of the process just copy this jar back to the lib directory and rename it to the name of the in jar
  • Libraries - include all libraries in the lib file except for your own jars.
  • keep - any package-name, class, method or field that needs to keep its name. Common things that you should keep in a webapp:
    • controller method names.
    • any class that is used by play and specified in the application.conf like ErrorHandler, ApplicationLoader
    • All router-generated classes

So my configuration file called myConfig.pro, should like something like this:

# specify the input jars, output jars, and library jars. ::::::::::::::::::::::::::::::
-injars /home/koala/Scaricati/play-java-starter-example/target/universal/play-java-starter-example-1.0-SNAPSHOT/lib/play-java-starter-example.play-java-starter-example-1.0-SNAPSHOT-sans-externalized.jar(!META-INF)
-outjars /home/koala/Scaricati/obfuscated.jar
-libraryjars /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
-libraryjars /home/koala/Scaricati/play-java-starter-example/target/universal/play-java-starter-example-1.0-SNAPSHOT/lib/
#-libraryjars /path/to/lib/library1.jar
#-libraryjars /path/to/lib/library2.jar

# keep ::::::::::::::::::::::::::::::
-keepnames class com.example.ErrorHandler
-keepnames class com.example.ApplicationLoader    
-keepnames class controllers.**

-keepclassmembernames class controllers.** {
    <methods>;    
}
-keeppackagenames controllers.**, router.**, views.**
-keep class router.** {*;}
-keepnames class router.** {*;}
-keepclassmembers class router.** {*;}
-keep class views.** {*;}
-keepnames class views.** {*;}
-keepclassmembers class views.** {*;}

After obfuscation is done and you copied the output jar back to its old dir and name, you can zip back your project and you’ve got an obfuscated play project.

I tried to apply this method using the simple Java starter example downloaded from here:
https://www.playframework.com/download#starters

Everything went well but when I tried to run the obfuscated project in production mode (through the corresponding autogenerated script in the bin folder):

/your-app-dir/target/universal/your-app-1.0-SNAPSHOT/bin/your-app -Dhttp.port = 9000

I got the following error:

[warn] o.h.v.m.ParameterMessageInterpolator - HV000184: ParameterMessageInterpolator has been chosen, EL interpolation will not be supported
Oops, cannot start the server.
Configuration error: Configuration error[Cannot load class filters.ExampleFilter]
	at play.api.Configuration$.configError(Configuration.scala:156)
	at play.api.Configuration.reportError(Configuration.scala:990)
	at play.api.http.EnabledFilters.$anonfun$filters$2(HttpFilters.scala:90)
	at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
	at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:59)
	at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:52)
	at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
	at scala.collection.TraversableLike.map(TraversableLike.scala:234)
	at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
	at scala.collection.AbstractTraversable.map(Traversable.scala:104)
	at play.api.http.EnabledFilters.liftedTree1$1(HttpFilters.scala:84)
	at play.api.http.EnabledFilters.<init>(HttpFilters.scala:80)
	at play.api.http.EnabledFilters$$FastClassByGuice$$5bec3932.newInstance(<generated>)
	at com.google.inject.internal.DefaultConstructionProxyFactory$FastClassProxy.newInstance(DefaultConstructionProxyFactory.java:89)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:111)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
	at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
	at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56)
	at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
	at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
	at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56)
	at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
	at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
	at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
	at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56)
	at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205)
	at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1085)
	at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199)
	at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)
	at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
	at com.google.inject.Guice.createInjector(Guice.java:99)
	at com.google.inject.Guice.createInjector(Guice.java:84)
	at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:185)
	at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:137)
	at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
	at play.core.server.ProdServerStart$.start(ProdServerStart.scala:51)
	at play.core.server.ProdServerStart$.main(ProdServerStart.scala:25)
	at play.core.server.ProdServerStart.main(ProdServerStart.scala)
Caused by: java.lang.ClassNotFoundException: filters.ExampleFilter
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at play.api.http.EnabledFilters.$anonfun$filters$2(HttpFilters.scala:86)
	... 51 more

I think it is due to an error or omission in the myConfig.pro configuration file.
Can someone help me?
Does anyone know a simpler way to obfuscate a Play project?

I suspect you are going to also need config for components used by the play framework like
scala (old): https://www.guardsquare.com/en/proguard/manual/examples#scala
akka (old): https://gist.github.com/bjornharrtell/3307987

Ideally there would be a place where config for the latest version of play/scala/akka was kept up to date by the community.

After hitting a similar error, I realized the output of proguard probably needs 100% test coverage to catch load errors after upgrading major components before they went out to production.

Thanks for your answer.
However I think this is not the problem.
I keep getting the same error when I try to run the project obfuscated in production mode.

@Fobi Ya, I only intended warn you of the possibility that you may have many more “cannot load class errors” behind that one for components used internally by playframework for which I could only find old out of date config. Trying to get it working on the example is probably the right place to start so hopefully someone who has some proguard experience can suggest how to get through the current error. Having a working proguard config for the current version of a play sample would be good for the community.

1 Like

Would be great to get a definitive recipe on how to do this using the sbt-proguard plugin. I’ve been using Play on some IoT stuff, and the package footprint is getting quite high, would be awesome to be able to compress it easily.