Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Java's method of protecting source code through encryption

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly explains "how to understand Java's method of protecting source code through encryption technology". The content of the article is simple and clear, and it is easy to learn and understand. below, please follow the editor's train of thought slowly and deeply, to study and learn "how to understand Java's method of protecting source code through encryption technology".

First, why should it be encrypted?

For traditional languages such as C or C++, it's easy to protect the source code on Web, as long as you don't release it. Unfortunately, the source code of the Java program is easy for others to peek at. As long as there is a decompiler, anyone can analyze other people's code. The flexibility of Java makes the source code easy to steal, but at the same time, it also makes it relatively easy to protect the code through encryption. The only thing we need to know is the ClassLoader object of Java. Of course, knowledge of Java Cryptography Extension (JCE) is also essential in the encryption process.

There are several techniques that can "blur" Java class files, which greatly reduces the effectiveness of decompilers in dealing with class files. However, it is not difficult to modify the decompiler to deal with these obfuscated class files, so you can't simply rely on fuzzy technology to ensure the security of the source code.

We can encrypt applications with popular encryption tools, such as PGP (Pretty Good Privacy) or GPG (GNU Privacy Guard). At this point, the end user must decrypt the application before running it. But after decryption, the end user has an unencrypted class file, which is no different from not encrypting it in advance.

The mechanism for loading bytecode at Java runtime implicitly means that bytecode can be modified. Each time JVM loads a class file, it needs an object called ClassLoader, which is responsible for loading the new class into the running JVM. JVM gives ClassLoader a string containing the name of the class to be loaded (such as java.lang.Object), and ClassLoader is responsible for finding the class file, loading the raw data, and converting it into a Class object.

We can modify the class file before it is executed by customizing the ClassLoader. This technology is widely used-in this case, it is used to decrypt class files when they are loaded, so it can be seen as an instant decryptor. Because the decrypted bytecode file will never be saved to the file system, it is difficult for the thief to get the decrypted code.

Since the process of converting the original bytecode into a Class object is entirely the responsibility of the system, it is not difficult to create a custom ClassLoader object, as long as you get the original data first, and then any transformation, including decryption, can be performed.

Java 2 simplifies the construction of custom ClassLoader to some extent. In Java 2, the default implementation of loadClass is still responsible for handling all the necessary steps, but it also calls a new findClass method to take into account the various custom class loading procedures.

This gives us a shortcut to writing custom ClassLoader and reduces the hassle: just overwrite findClass, not loadClass. This approach avoids repeating the common steps that all loaders must perform, because loadClass is responsible for all of this.

However, the custom ClassLoader for this article does not use this approach. The reason is simple. If the default ClassLoader looks for the encrypted class file first, it can find it; but because the class file is encrypted, it will not recognize the class file, and the loading process will fail. Therefore, we have to implement loadClass ourselves, adding a little bit of work.

II. Custom class loader

Every running JVM already has a ClassLoader. This default ClassLoader looks for the appropriate bytecode file in the local file system based on the value of the CLASSPATH environment variable.

The application of customized ClassLoader requires a deeper understanding of this process. We must first create an instance of a custom ClassLoader class, and then explicitly ask it to load another class. This forces JVM to associate this class and all the classes it needs to the custom ClassLoader. Listing 1 shows how to load class files with custom ClassLoader.

[Listing 1: load class files with custom ClassLoader]

/ / first create a ClassLoader object ClassLoader myClassLoader = new myClassLoader (); / / load the class file using a custom ClassLoader object / / and convert it into a Class object Class myClass = myClassLoader.loadClass ("mypackage.MyClass"); / / finally, create an instance of the class Object newInstance = myClass.newInstance (); / / Note that all other classes required by MyClass will be automatically loaded through / / custom ClassLoader

As mentioned earlier, customizing the ClassLoader simply takes the data from the class file and then passes the bytecode to the runtime system, which does the rest of the task.

ClassLoader has several important methods. When creating a custom ClassLoader, we only need to overwrite one of them, loadClass, providing the code to get the data from the original class file. This method takes two parameters: the name of the class, and a tag indicating whether JVM requires the class name to be resolved (that is, whether the dependent class is loaded at the same time). If the tag is true, we just need to call resolveClass before returning JVM.

[a simple implementation of Listing 2:ClassLoader.loadClass ()]

Public Class loadClass (String name, boolean resolve) throws ClassNotFoundException {try {/ / the Class object we are going to create Class clasz = null; / / required step 1: if the class is already in the system buffer, / / we do not have to load it again clasz = findLoadedClass (name); if (clasz! = null) return clasz; / / below is the customized part byte classData [] = / * get bytecode data in some way * / If (classData! = null) {/ / successfully read the bytecode data, now convert it to a Class object clasz = defineClass (name, classData, 0, classData.length);} / / required step 2: if the above is not successful, / / We try to load it with the default ClassLoader if (clasz = = null) clasz = findSystemClass (name) / / required step 3: load the relevant class if (resolve & & clasz! = null) resolveClass (clasz) if necessary; / / 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 a small portion (marked with comments) is unique. During processing, the ClassLoader object uses several other helper methods:

FindLoadedClass: used to check to confirm that the requested class does not currently exist. The loadClass method should call it first.

DefineClass: after getting the bytecode data of the original class file, 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 deliberately does not need a custom method), you can call this method to try the default loading method. This is useful, especially when loading standard Java classes from normal JAR files.

ResolveClass: when JVM wants to load not only the specified class, but also all other classes referenced by that class, it sets the resolve parameter of loadClass to true. At this point, we must call resolveClass before returning the Class object we just loaded to the caller.

III. Encryption and decryption

The Java encryption extension is Java Cryptography Extension, or JCE for short. It is Sun's encryption service software, including encryption and key generation functions. JCE is an extension of JCA (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 includes SunJCE service providers, including many useful encryption algorithms, such as DES (Data Encryption Standard) and Blowfish.

For simplicity, in this article we will use the DES algorithm to encrypt and decrypt bytecode. Here are the basic steps that you must follow to encrypt and decrypt data with JCE:

Step 1: generate a security key. You need to have a key before encrypting or decrypting any data. A key is a small piece of data released along with an encrypted application, and Listing 3 shows how to generate a key.

[Listing 3: generate a key]

/ / DES algorithm requires a trusted random number source SecureRandom sr = new SecureRandom (); / / generate a KeyGenerator object KeyGenerator kg = KeyGenerator.getInstance ("DES"); kg.init (sr) for our chosen DES algorithm; / / generate key SecretKey key = kg.generateKey (); / / get key data byte rawKeyData [] = key.getEncoded () / * you can then encrypt or decrypt it with a key, or save it as a file for later use * / doSomething (rawKeyData)

Step 2: encrypt the data. Once you have the key, you can then use it to encrypt the data. In addition to the decrypted ClassLoader, there is generally a separate program that encrypts the application to be released (see Listing 4).

[Listing 4: encrypt raw data with keys]

/ / the DES algorithm requires a trusted random number source SecureRandom sr = new SecureRandom (); byte rawKeyData [] = / * get the key data in some way * /; / / create a DESKeySpec object DESKeySpec dks = new DESKeySpec (rawKeyData) from the original key data; / / create a key factory and use it to convert DESKeySpec into / / a SecretKey object SecretKeyFactory keyFactory = SecretKeyFactory.getInstance ("DES") SecretKey key = keyFactory.generateSecret (dks); / / the Cipher object actually completes the encryption operation Cipher cipher = Cipher.getInstance ("DES"); / / initializes the Cipher object cipher.init (Cipher.ENCRYPT_MODE, key, sr) with the key; / / now, get the data and encrypt byte data [] = / * get the data in some way * / formally perform the encryption operation byte encryptedData [] = cipher.doFinal (data) / / further process the encrypted data doSomething (encryptedData)

Step 3: decrypt the data. When running an encrypted application, ClassLoader parses and decrypts class files. The procedure is shown in Listing 5.

[Listing 5: decrypt data with key]

/ / the DES algorithm requires a trusted random number source SecureRandom sr = new SecureRandom (); byte rawKeyData [] = / * get the raw key data in some way * /; / / create a DESKeySpec object DESKeySpec dks = new DESKeySpec (rawKeyData) from the original key data; / / create a key factory and use it to convert the DESKeySpec object into / / a SecretKey object SecretKeyFactory keyFactory = SecretKeyFactory.getInstance ("DES") SecretKey key = keyFactory.generateSecret (dks); / / the Cipher object actually completes the decryption operation Cipher cipher = Cipher.getInstance ("DES"); / / initializes the Cipher object cipher.init (Cipher.DECRYPT_MODE, key, sr) with the key; / / now, get the data and decrypt byte encryptedData [] = * get the encrypted data * / / formally perform the decryption operation byte decryptedData [] = cipher.doFinal (encryptedData) / / further process the decrypted data doSomething (decryptedData)

IV. Application examples

Earlier, I described how to encrypt and decrypt data. To deploy an encrypted application, the steps are as follows:

Step 1: create an application. Our example contains an App main class and two auxiliary classes (called Foo and Bar, respectively). This application has no practical function, but as long as we can encrypt this application, we can encrypt other applications.

Step 2: generate a security key. On the command line, use the GenerateKey tool (see GenerateKey.java) to write the key to a file:% java GenerateKey key.data

Step 3: encrypt the application. On the command line, encrypt the applied class using the EncryptClasses tool (see EncryptClasses.java):% java EncryptClasses key.data App.class Foo.class Bar.class

This command replaces each .class file with its own encrypted version.

Step 4: run the encrypted application. Users run encrypted applications through a DecryptStart program. The DecryptStart program is shown in Listing 6.

[Listing 6:DecryptStart.java, start the encrypted application]

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, / / later the loadClass () method will use them to decrypt the class private SecretKey key; private Cipher cipher / / Constructor: set the object public DecryptStart (SecretKey key) throws GeneralSecurityException, IOException {this.key = key; String algorithm = "DES"; SecureRandom sr = new SecureRandom (); System.err.println ("[DecryptStart: creating cipher]"); cipher = Cipher.getInstance (algorithm); cipher.init (Cipher.DECRYPT_MODE, key, sr) } / / main process: we will read in the key here and create a / / instance of DecryptStart, which is our custom ClassLoader. / / after ClassLoader is set, we use it to load the application instance. / / finally, we call the main method static public void main (String args []) throws Exception {String keyFilename = args [0]; String appName = args [1] of the application instance through Java Reflection API; / / these are the parameters passed to the application itself: String realArgs [] = new string [args.length-2]; System.arraycopy (args, 2, realArgs, 0, args.length-2) / / read 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 decrypted ClassLoader DecryptStart dr = new DecryptStart (key) / / create an instance of the application main class / / load it System.err.println ("[DecryptStart: loading" + appName+ "]") through ClassLoader; Class clasz = dr.loadClass (appName); / / finally, call the main () method of the application instance / / via Reflection API / / to get a reference to main () [] = new String [1] Class mainArgs [] = {(new String [1]) .getClass ()}; Method main = clasz.getMethod ("main", mainArgs); / / create an array of main () method parameters Object argsArray [] = {realArgs}; System.err.println ("[DecryptStart: running" + appName+ ".main ()]"); / / call main () main.invoke (null, argsArray) } public Class loadClass (String name, boolean resolve) throws ClassNotFoundException {try {/ / the Class object we are going to create Class clasz = null; / / required step 1: if the class is already in the system cache / / We don't have to load it again clasz = findLoadedClass (name); if (clasz! = null) return clasz / / the following is a customized part of try {/ / read the encrypted class file byte classData [] = Util.readFile (name+ ".class"); if (classData! = null) {/ / decrypt. Byte decryptedClassData [] = cipher.doFinal (classData); / / Then convert it to a class clasz = defineClass (name, decryptedClassData, 0, decryptedClassData.length); System.err.println ("[DecryptStart: decrypting class" + name+ "]");}} catch (FileNotFoundException fnfe) {} / / required step 2: if the above is not successful / / We try to load it with the default ClassLoader if (clasz = = null) clasz = findSystemClass (name) / / required step 3: load the relevant class if (resolve & & clasz! = null) resolveClass (clasz) if necessary; / / return the class to the caller return clasz;} catch (IOException ie) {throw new ClassNotFoundException (ie.toString ());} catch (GeneralSecurityException gse) {throw new ClassNotFoundException (gse.toString ());}

DecryptStart has two purposes. An example of DecryptStart is a custom ClassLoader; that implements instant decryption operations, while DecryptStart also includes a main process that creates a decryptor instance and uses it to load and run applications. The code for the sample application App is contained in App.java, Foo.java, and Bar.java. Util.java is a file Iamp O tool, which is used in many of the examples in this article.

V. matters needing attention

We see that it is easy to encrypt a Java application without modifying the source code. But there is no completely secure system in the world. The encryption method in this article provides a degree of source code protection, but it is vulnerable to some attacks.

Although the application itself is encrypted, the launcher DecryptStart is not encrypted. An attacker can decompile the startup program and modify it to save the decrypted class file to disk. One way to reduce this risk is to obfuscate the startup program with high quality. Alternatively, the startup program can also use code that is compiled directly into the machine language, making the startup program as secure as the traditional execution file format.

Also keep in mind that most JVM are not inherently secure. A cunning hacker may modify JVM to obtain decrypted code from outside ClassLoader and save it to disk, thereby bypassing the encryption techniques of this article. Java did not provide a really effective remedy for this.

It should be noted, however, that all these possible attacks have a premise that the attacker can get the key. If there is no key, the security of the application depends entirely on the security of the encryption algorithm. Although this method of protecting code is not perfect, it is still an effective way to protect intellectual property rights and sensitive user data.

Thank you for your reading, the above is the content of "how to understand Java's method of protecting source code through encryption technology". After the study of this article, I believe you have a deeper understanding of how to understand Java's method of protecting source code through encryption technology. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report