Obfuscating Javascript with Maven and Google Closure

GitHub Repository – with Maven project example

At the time of writing I couldn’t find an easy way to automate Javascript obfuscation when building a Maven project. Using the Googles Closure compiler I decided to work on my own concept. This guide shows how you can incorporate the same process into your maven projects.

EDIT: wro4j is a plugin that makes use of the Closure compiler however it only allows for SIMPLE_CUSTOMIZATIONS level obfuscation.

EDIT: As per Alex’s comment below it seems you can customise the wro4j plugin

Firstly, you need to include the Exec-Maven. This plugin allows you to execute Java applications during your build process. Include this plugin in your maven pom.xml like so:

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>exec-maven-plugin</artifactId>
	<version>1.1</version>
	<executions>
  		<execution>
    			<goals>
      				<goal>java</goal>
    			</goals>
  		</execution>
	</executions>
	<configuration>
		<mainClass>uk.co.eggsylife.JavascriptObfuscator</mainClass>
		<arguments>
			<argument>${basedir}/src/main/webapp/scripts/raw.js</argument> <!-- Input file -->
			<argument>${basedir}/src/main/webapp/scripts/raw.min.js</argument> <!-- Output file -->
		</arguments>
	</configuration>
</plugin>

Notice the configuration of the plugin. Firstly we are targeting the goal ‘java’. I shall discuss this later. Secondly, we define a main class. This is the Java application the plugin will run. Finally we have the arguments, these are the arguments that will be passed to the ‘public static void main(String[] args)’ method. In the example above we pass the input file and the location/name of the output file.

Now for the ‘JavascriptObfuscator’ class. This class communicates with the Closure Compiler API passing in the raw Javascript from the input file – asking for the obfuscated version from the API and then saving the output to the specified output file.

package uk.co.eggsylife;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
 
public class JavascriptObfuscator {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
 
		System.out.println("Obfuscating Javascript");
 
		if( args.length >= 2 ) {
 
			int argumentCount = args.length;
			String jsFile = args[0];
 
			String outputFile = args[1];
 
			String compilationLevel = "ADVANCED_OPTIMIZATIONS";
			if(argumentCount == 3 ) {
				compilationLevel  = args[2];
			}
 
 
			String outputFormat = "text";
			if(argumentCount == 4 ) {
				outputFormat  = args[3];
			}
 
			System.out.println("Processing ( " + jsFile + " )" );
			System.out.println("Compilation Level ( " + compilationLevel + " )");
			System.out.println("Output Format ( " + outputFormat + " )");
			System.out.println("Saving to ( " + outputFile + " )");
 
			StringBuffer contents = new StringBuffer();
			BufferedReader reader = null;
 
			try {
				File file = new File(jsFile);
	            		reader = new BufferedReader(new FileReader(file));
			    	String text = null;
 
			    	// Read the Javascript
			    	while ((text = reader.readLine()) != null) {
			        	contents.append(text).append(System.getProperty("line.separator"));
			    	}
 
				// Javascript Pre-Obfuscation
			    	String jsCode = contents.toString();
 
			    	ClosureCompiler compiler = new ClosureCompiler(outputFile, jsCode, compilationLevel, outputFormat);
			    	compiler.processCompilation();
 
			    	System.out.println("Obfuscation Finished");
 
			} 
			catch (Exception e) {
	            		e.printStackTrace();
	        	}
			finally {
				try {
	                		if (reader != null) {
	                			reader.close();
	                		}
	            		} 
				catch (IOException e) {
	                		e.printStackTrace();
	            		}
	        	}
 
		}
		else {
			System.err.println("No (not enough) arguments passed to Obfuscation - requires JS file and Output directory");
		}
	}
}

The class *must* have at least two arguments – the input and output file – however you can pass further arguments which allow you to customise the Closure compiler obfuscation.

  • args[0] = Input file (required)
  • args[1] = Output file (required)
  • args[2] = Compilation level (default ADVANCED_OPTIMIZATIONS)
  • args[3] = Output format (default test)

You can of course customise this class to make it perform as you like

Next notice the use of the ‘ClosureCompiler’ class, this class communicates with the Closure API

package uk.co.eggsylife;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
 
public class ClosureCompiler {
 
	private String outputFile = null;
	private String jsCode = null;
	private String compilationLevel = null;
	private String outputFormat = null;
 
	public ClosureCompiler(String outputFile, String jsCode, String compilationlevel, String outputFormat) {
		this.outputFile = outputFile;
		this.jsCode = jsCode;
		this.compilationLevel = compilationlevel;
		this.outputFormat = outputFormat;
	}
 
	public void processCompilation() {
		try {
			String obfuscatedCode = sendForCompilation();
			saveObfuscatedCodeToFile(outputFile, obfuscatedCode);
		} 
		catch (Exception e) {
			System.err.println("Exception when obfuscating code: " + e.getMessage());
		}
	}
 
	private String sendForCompilation() throws Exception {
		URL url = new URL("http://closure-compiler.appspot.com/compile");
 
		String data = URLEncoder.encode("js_code", "UTF-8") + "=" + URLEncoder.encode(jsCode, "UTF-8");
		data += "&" + URLEncoder.encode("compilation_level", "UTF-8") + "=" + URLEncoder.encode(compilationLevel, "UTF-8");
		data += "&" + URLEncoder.encode("output_format", "UTF-8") + "=" + URLEncoder.encode(outputFormat, "UTF-8");
		data += "&" + URLEncoder.encode("output_info", "UTF-8") + "=" + URLEncoder.encode("compiled_code", "UTF-8");
 
		URLConnection conn = url.openConnection();
		conn.setDoOutput(true);
		OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
		wr.write(data);
		wr.flush();
 
		// Get the response
		BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
		String codeLine;
 
		StringBuffer obfuscatedCodeLine = new StringBuffer();
		while ((codeLine = rd.readLine()) != null) {
			obfuscatedCodeLine.append(codeLine).append(System.getProperty("line.separator"));
		}
		wr.close();
		rd.close();
 
		return obfuscatedCodeLine.toString();
	}
 
	private void saveObfuscatedCodeToFile(String outputdirectory, String javascriptCode) throws Exception{
		FileWriter fstream = new FileWriter(outputdirectory);
		BufferedWriter out = new BufferedWriter(fstream);
		out.write(javascriptCode);
		//Close the output stream
		out.close();
	}
 
}

It fetches the obfuscated result and saves it to the specified output file

That just about completes the blog, the last thing is to show how your would invoke this. Thats a simple as:

mvn exec:java

You can of course customise the goal of the plugin so that it obfuscates as and when you need it to.

I would also welcome any comments or further customisations on this

One thought on “Obfuscating Javascript with Maven and Google Closure

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>