Java Bytecode Encryption Revisited

In my recent article on obfuscators and other Java code protection means, I dismiss Java bytecode encryption on the basis that it is easy to circumvent. In short, the JVM obviously may not execute encrypted bytecode, and the decrypted classes may be intercepted fairly easily when the JVM loads them. However, my visit to the JavaOne 2008 Pavilion has prompted me to revisit the topic (pun intended.)

In the Pavilion, a U.S. company called Validy Net Inc. had been showcasing the device pictured on the right, branded Validy SoftNaos. As you may see, it looks like an oversized USB memory stick, but actually it is an USB token containing a slave co-processor that receives a flow of encrypted instructions from the application running on the PC. Those instructions interact with a subset of the application state, also stored in the token. This architecture enables you to hide a small but essential part of your application completely from prying eyes.

From the O/S point of view, the token looks like a smart card, and Java SE 6 conveniently implements the Java Smart Card I/O API (JSR-268). Combined with annotations, it has enabled Validy to implement Java application protection in a nearly transparent manner.

Basic usage

First, you use annotations to mark the methods and fields that you want to protect:


import com.validy.technology.annotation.*;

public class Minimal {
	
	@SecureField
	private int counter = 0;
	
	@SecureMethod
	public void inc() {
		counter++;
	}
}

and compile your classes as usual.

Then you run the resulting class files or jars through the Validy SoftNaos post-processor. It translates the code of secure methods into the token processor’s instruction set, encrypts the result, and replaces the bodies of secure methods with sequences of calls transmitting the encrypted instructions to the token for execution. The parts of non-secure methods accessing secure fields are transformed similarly.

Here is the output produced by a Java decompiler from the post-processed class file of the above sample:


import com.validy.card.ValidyException;
import com.validy.technology.runtime.*;

public class Minimal
    implements Secured
{

    public Minimal(int i)
    {
        k$This = new Memory(i);
        Token.out(com.validy.technology.runtime.Secured.Pointer.get(this));
        Token.exe(0xce52da1a62f69204L);
        Token.exe(0x68d563976d888a64L);
        Token.exe(0x9aaa432570daf701L);
    }

    public Minimal()
    {
        this(1);
    }

    public void inc()
    {
        Token.exe(0x200e5d55a842110aL);
        Token.exe(0xdda147e8d0d556c1L);
        Token.exe(0x74256cd9420d9741L);
        Token.exe(0x6f53e373761060adL);
        Token.exe(0xde11625c9a743f29L);
    }
}

As you may see, the post-processor has produced valid Java bytecode that did not break the decompiler. In fact, the output is verifiable Java bytecode that should work on any compliant Java SE 6 implementation, such as Excelsior JET. The nice people at the Validy booth have kindly provided me with a sample token that I played with after returning from the trip, and it worked flawlessly with our product.

Performance And Field Of Use

Everything comes with a price however. In this case, as you may have guessed, the price is the radical slowdown of secured methods and access to secured fields. The token processor does much less instructions per second that the main CPU, and it also has to decrypt the instructions flowing in! So it comes as no surprise that the speed of code running on the token is by several orders of magnitude lower than the speed of the original code running on a modern JVM. I have transformed one of the samples from the SoftNaos evaluation package into a simple benchmark, and the secure version run almost 40,000 times slower on my system.

Of course, in a more realistic (i.e. larger) application, the fraction of code executed inside the token and likewise the decrease in performance can be much smaller while still providing very strong protection.

Therefore, in practice you would apply the Validy solution only to those portions of application code/state for which security and tamper resistance are at least 40,000 times more important than performance. Prevention of piracy, unfair use, and tampering seem to be easy to implement. And with some additional programming this solution can also help you build an additional line of defense against data security breaches, and so on.

Synergy

Why have I decided to write about Validy SoftNaos in the first place? Technically, it is a pure Java solution built on top of a standard Java SE 6 API, so Excelsior JET supports it just like any other Java API. I think it complements nicely the Java code protection capabilities of our product. By carefully selecting the sensitive methods and fields that deserve maximum protection with Validy SoftNaos and then compiling the entire application to native code with Excelsior JET, you may reach an unprecedented level of Java software protection. (Add a name obfuscator such as ProGuard to this toolchain if you are totally paranoid.)

To learn more about Validy SoftNaos and download the free simulation/evaluation package, visit the Validy SoftNaos home page.

Categories: Excelsior Installer, Excelsior JET, Java

Comments are closed.