content:
First, why do you want to encrypt?
Second, custom class loader
Third, encryption, decryption
Fourth, application examples
V. Precautions
Reference resource
The source code of the Java program is easily peeled by others. As long as there is an anti-compiler, anyone can analyze the code of others. This article discusses how to protect source code by encryption technology without modifying the original program.
First, why do you want to encrypt?
For languages such as traditional C or C , it is easy to protect source code on the web, as long as it does not release it. Unfortunately, the source code of the Java program is easily peeked by others. As long as there is an anti-compiler, anyone can analyze the code of others. The flexibility of Java makes the source code are easily stealing, but at the same time, it also makes it relatively easy to encrypt the code, and the only thing we need to know is Java's ClassLoader object. Of course, in the encryption process, knowledge about Java Cryptography Extension (JCE) is essential.
There are several technologies that can "fuzzy" Java files that make the anti-compiler handle the effect of the class file. However, modifying the anti-compiler makes it possible to handle these fuzzy-processed class files, so it is not easy to rely on fuzzy techniques to ensure the security of the source code.
We can encrypt applications, such as PGP (PRETTY Good Privacy) or GPG (GNU Privacy Guard). At this time, the end user must first decrypt before running the application. But after decryption, the end user has a non-encrypted class file, which does not differ in advance.
The mechanism of Java runtime installation is implicitly implicitly implicitly modified. JVM requires an object called ClassLoad every time you load a class file, which is responsible for putting new classes into running JVM. JVM gives the ClassLoader a string containing the name of the name (such as a java.lang.object), then by the ClassLoader to find the class file, load the original data, and convert it into a Class object.
We can modify it before the class file is executed by custom ClassLoader. The application of this technology is very broad. Here, its use is to decrypt when the class file is loaded, so it can be seen as an instant decryption device. Since the decrypted bytecode file will never save to the file system, the screwdriver is hard to get the decrypted code.
Since the process of converting the original byte code into a Class object is completely responsible for the system, create a custom ClassLoader object is not difficult, just get the original data first, then you can make any conversion in the decryption.
Java 2 simplifies custom ClassLoader builds to a certain extent. In Java 2, LoadClass's default implementation is still responsible for processing all required steps, but in order to take into account various customized class equipment, it also calls a new FindClass method.
This provides a shortcut to our customized ClassLoader, which reduces trouble: just override FindClass instead of overwriting loadingClass. This approach avoids the public steps necessary to repeat all loaders because all of this is responsible by loadingClass.
However, the custom ClassLoader herein does not use this method. the reason is simple. If you look for encrypted class files by the default classloader, it can be found; but because the class file has been encrypted, it will not recognize this class file, and the load process will fail. Therefore, we must implement loadclass themselves, and some workload has increased slightly.
Second, custom class loader
Each running JVM already has a ClassLoader. This default ClassLoader is looking for a suitable bytecode file in a local file system based on the value of the ClassPath environment variable. Application Custom ClassLoader requires a more in-depth understanding of this process. We must first create an instance of custom ClassLoader classes, and then explicitly require it to load another class. This enables the JVM to associate this class and all the classes they need to customized ClassLoader. Listing 1 shows how to load class files with custom ClassLoader.
[Listing 1: Loading class files with custom ClassLoad)
// First create a ClassLoader object
ClassLoader MyclassLoader = new myclassloader ();
// Load the class file with custom ClassLoader objects
/ / Convert it into a Class object
Class myclass = myclassloader.loadclass ("mypackage.myclass");
// Finally, create an instance of such a class
Object newinstance = myclass.newinstance ();
// Note that all other classes needed by MyClass will pass
// Customized ClassLoader Automatic loading
As mentioned earlier, custom ClassLoad is just acquire data of class files, and then pass the byte code to the runtime system, which completes the remaining tasks by the latter.
ClassLoader has several important ways. When you create a custom ClassLoader, we only need to overwrite one of them, that is, LoadClass, providing code for obtaining the original class file data. This method has two parameters: the name of the class, and a mark indicating whether the JVM requires the parsing class name (ie, whether it is equally loaded with a dependency). If this tag is True, we only need to call ResolVeclass before returning JVM.
[Listing 2: ClassLoader.loadClass () A simple implementation]
Public Class LoadClass (String Name, Boolean Resolve)
Throws classnotfoundexception {
Try {
// We want to create a Class object
Class Clasz = NULL;
/ / Essential Step 1: If the class is already in the system buffer,
// We don't have to load it again
Clasz = findloadedclass (name);
IF (CLASZ! = NULL)
Return Clasz;
// below is a custom section
Byte classdata [] = / * acquires bytecode data * /;
IF (ClassData! = null) {
/ / Successfully read bytecode data, now convert it into a Class object
Clasz = DefineClass (Name, ClassData, 0, ClassData.length);
}
/ / Essential Step 2: If there is no success,
// We tried to load it with the default classloader
IF (CLASZ == NULL)
Clasz = FINDSYSTEMCLASS (NAME);
/ / Required step 3: If necessary, load the relevant class
IF (Resolve && Clasz! = NULL)
ResolVeclass (Clasz);
// Return the class to the caller
Return Clasz;
} catch (ioexception ie) {
Throw new classnotfoundexception (ie.tostring ());} catch (generalsecurityexception gse) {
Throw new classnotfoundexception (gse.tostring ());
}
}
Listing 2 shows a simple loadClass implementation. Most of the code is the same for all ClassLoader objects, but there is a small part (marked by comment marks). During the processing, the ClassLoader object is used to use several other auxiliary methods:
FindloadedClass: It is used to check to confirm that the requested class does not currently exist. The LoadClass method should first call it.
DefineClass: After getting the original class file number data, call DefineClass to convert it into a Class object. Any LoadClass implementation must call this method.
FindsystemClass: Provides support for default ClassLoader. If the custom method used to find the class cannot find the specified class (or intentionally do not customize the custom method), you can call the method to try the default loading method. This is useful, especially when loading standard Java classes from ordinary JAR files.
ResolVeclass: When the JVM wants to load not only includes the specified class, but also when all other classes referenced, it sets the LoadClass's Resolve parameter to TRUE. At this time, we must call ResolVeclass before returning to the originally loaded CLASS object to call the caller.
Third, encryption, decryption
Java encryption extension is Java Cryptography Extension, referred to as JCE. It is Sun's encryption service software that includes encryption and key generation functions. JCE is an extension of JAVA CRYPTOGRAPHY ARCHITECTURE.
JCE does not specify a specific encryption algorithm, but provides a framework, and the specific implementation of the encryption algorithm can be added as a service provider. In addition to the JCE framework, the JCE package also includes a SunJCE service provider, including many useful encryption algorithms, such as DES (Data Encryption Standard) and Blowfish.
For a simple meter, in this article we will encrypt and decrypt by the DES algorithm. Below is the basic steps that must be followed by JCE encryption and decryption data:
Step 1: Generate a security key. There is a key before encrypt or decrypt any data. The key is a small data released with the encrypted application, and Listing 3 shows how to generate a key. [Listing 3: Generate a key]
// DES algorithm requires a trusted random number
Securerandom SR = New SecurerandM ();
/ / Generate a keygenerator object for our choice of the DES algorithm
KeyGenerator kg = keygenerator.getInstance ("des");
Kg.init (SR);
// Generate a key
SecretKey Key = kg.generateKey ();
// Get a key data
Byte rawkeydata [] = key.getencoded ();
/ * Next, you can encrypt or decrypt it with a key, or save it.
Use the file for the file * /
DOSMETHING (RawkeyData);
Step 2: Encrypt the data. After getting a key, you can use it to encrypt the data. In addition to decrypting ClassLoader, there is generally a separate program that encrypts the application (see Listing 4). [Listing 4: Use the key to encrypt the original data] // DES algorithm requires a trusted random number
Securerandom SR = New SecurerandM ();
Byte RawkeyData [] = / * Get the key data in some way * /;
/ / Create a deskeyspec object from the original key data
Deskeyspec DKS = New Deskeyspec (RawkeyData);
// Create a key factory, then use it to convert Deskeyspec into
// A SecretKey object
SecretKeyFactory KeyFactory = SecretKeyFactory.GetInstance ("des");
SecretKey Key = KeyFactory.GenerateSecret (DKS);
// Cipher object actually completed encryption operations
Cipher cipher = cipher.getinstance ("des");
// initialize the Cipher object with a key
Cipher.init (Cipher.Encrypt_Mode, Key, SR);
// Now get data and encrypt
Byte data [] = / * Get data in some way * /
/ / Officially perform encryption operation
Byte EncryptedData [] = Cipher.dofinal (DATA);
// further process the encrypted data
DOSMETHING (EncryptedData);
Step 3: Decrypt the data. ClassLoader analyzes and decrypts class files when running encrypted applications. The steps are shown in Listing 5. [Listing 5: Decrypt data with key]
// DES algorithm requires a trusted random number
Securerandom SR = New SecurerandM ();
Byte rawkeydata [] = / * Get the original key data in some way * /;
// Create a deskeyspec object from the original key data
Deskeyspec DKS = New Deskeyspec (RawkeyData);
// Create a key factory, then use it to convert the deskeyspec object into
// A SecretKey object
SecretKeyFactory KeyFactory = SecretKeyFactory.GetInstance ("des");
SecretKey Key = KeyFactory.GenerateSecret (DKS);
/ / Cipher object actually completes the decryption operation
Cipher cipher = cipher.getinstance ("des");
// initialize the Cipher object with a key
Cipher.init (Cipher.Decrypt_Mode, Key, SR);
// Now get data and decrypt
BYTE EncryptedData [] = / * Get encrypted data * /
/ / Officially perform decryption operation
Byte decrypteddata [] = cipher.dofinal (encryptedData);
// Further handle the decomposition data
DOSMETHING (DecryptedData);
Fourth, application examples
The previous introduction how to encrypt and decrypt data. To deploy an encrypted application, the steps are as follows:
Step 1: Create an app. Our example contains an App main class, two auxiliary classes (known as Foo and Bar, respectively). There is no practical function of this app, but as long as we can encrypt this app, encrypting other applications will not be in. Step 2: Generate a security key. In the command line, write the key to a file using the GenerateKey Tool (see GenerateKey.java):% Java GenerateKey Key.Data
Step 3: Encrypt App. In the command line, use EncryptClasses tools (see EncryptClasses.java) Class:% java encryptclasses key.data app.class foo.class bar.class
This command replaces each .class file with their respective encryption versions.
Step 4: Run the encrypted application. The user runs encrypted applications through a DecryptStart program. DecryptStart programs are shown in Listing 6. [Listing 6: DecryptStart.java, launch the program that is encrypted]]
Import java.io. *;
Import java.security. *;
Import java.lang.reflect. *;
Import javax.crypto. *;
Import javax.crypto.spec. *;
Public Class DecryptStart Extends ClassLoader
{
// These objects are set in the constructor,
// LoadClass () method later will use them to decrypt class
Private SecretKey Key;
PRIVATE CIPHER CIPHER;
// Constructor: Set the object required to decrypt
Public DecryptStart (SecretKey Key) throws generalsecurityException,
IOEXCEPTION {
THIS.KEY = KEY;
String algorithm = "des";
Securerandom SR = New SecurerandM ();
System.err.Println ("[DecryptStart: CREATING CIPHER]");
Cipher = Cipher.getInstance (Algorithm);
Cipher.init (Cipher.Decrypt_Mode, Key, SR);
}
// MAIN process: We have to read the key here, create DecryptStart
// Instance, it is our custom ClassLoader.
// After setting the ClassLoader, we use it to load the application instance.
// Finally, we call the application instance's main method through the Java Reflection API
Static public void main (string args []) throws exception {
String KeyFileName = args [0];
String appname = args [1];
/ / These are the parameters passing to the application itself
String realgs [] = new string [args.length-2];
System.ArrayCopy (Args, 2, Reaargs, 0, Args.Length-2);
// Read the key
System.err.Println ("[DecryptStart: Reading Key]");
Byte Rawkey [] = util.readfile (keyfilename); deskeyspec dks = new deskeyspec (rawkey);
SecretKeyFactory KeyFactory = SecretKeyFactory.GetInstance ("des");
SecretKey Key = KeyFactory.GenerateSecret (DKS);
// Create a decrypted ClassLoader
DecryptStart Dr = New DecryptStart (key);
// Create an instance of the application main class
// put it through ClassLoader
System.err.println ("[DecryptStart: Loading" AppName "]");
Class Clasz = Dr.LoadClass (Appname);
// Finally, the application example is called by the Reflection API.
// MAIN () method
/ / Get a reference to main ()
String prote [] = new string [1];
Class maings [] = {(new string [1]). GetClass ()};
Method main = Clasz.getMethod ("main", maings;
// Create an array containing the main () method parameter
Object argsarray [] = {reaargs};
System.err.println ("[DecryptStart: Running" AppName ". Main ()]");
// Call main ()
Main.invoke (null, argsarray);
}
Public Class LoadClass (String Name, Boolean Resolve)
Throws classnotfoundexception {
Try {
// We want to create a Class object
Class Clasz = NULL;
/ / Required step 1: If the class is already in the system buffer
// We don't have to load it again
Clasz = findloadedclass (name);
IF (CLASZ! = NULL)
Return Clasz;
// below is a custom section
Try {
// Read the encrypted class file
Byte classdata [] = util.readfile (name ". Class");
IF (ClassData! = null) {
// Decryption ...
BYTE DECRYPTEDCLASSDATA [] = Cipher.dofinal (ClassData);
// ... turn it into a class
Clasz = DefineClass (Name, DecryptedClassData,
0, DecryptedClassData.Length);
System.err.Println ("[DecryptStart: Decrypting Class" "]");
}
} catch (filenotfoundexception fnfe) {
}
/ / Essential Step 2: If there is no success
// We tried to load it with the default classloader
IF (CLASZ == NULL)
Clasz = FINDSYSTEMCLASS (NAME);
/ / Required step 3: If necessary, load the relevant class IF (Resolve && Clas! = NULL)
ResolVeclass (Clasz);
// Return the class to the caller
Return Clasz;
} catch (ioexception ie) {
Throw new classnotfoundexception (IE.toString ()
);
} catch (generalsecurityException gse) {
Throw new classnotfoundexception (gse.tostring ()
);
}
}
}
For uncrysed applications, normal execution is as follows:% Java App Arg0 Arg1 Arg2
For encrypted applications, the corresponding mode of operation is:% Java DecryptStart Key.Data App Arg0 Arg1 Arg2
DecryptStart has two purposes. An instance of a DecryptStart is a custom ClassLoader that implements instant decryption operations; at the same time, DecryptStart also contains a main process, which creates a decompressed instance and uses it to load and run applications. The code Application App to include app.java, foo.java and bar.java. Util.java is a file I / O tool that uses more examples in this example. Complete code Please download it from this article.
V. Precautions
We see that it is easy to encrypt a Java app without modifying the source code. However, there is no fully safe system in the world. This paper provides a certain degree of source code protection, but it is fragile for some attacks.
Although the application itself has been encrypted, the starter DecryptStart is not encrypted. An attacker can refactor to build the launcher and modify it, save the decrypted class file to disk. One way to reduce this risk is to perform high quality fuzzy processing on the startup program. Alternatively, the startup program can also be used directly into a machine language, so that the launcher has the security of traditional execution file formats.
In addition, it is necessary to remember that most JVM itself is not safe. The hacker may modify the JVM, obtain the decrypted code from the ClassLoader and save to disk, thus bypass the encryption technology of this article. Java did not provide truly effective remedies for this purpose.
However, it should be noted that all these possible attacks have a premise, which is the attacker can get a key. If there is no key, the security of the application is entirely on the security of the encryption algorithm. Although this protective code is not perfect, it is still not a valid program for protecting intellectual property and sensitive user data.