Red de conocimiento del abogados - Respuesta jurídica de la empresa - ¿Cómo evitar eficazmente que otros vean el código fuente del programa Java?

¿Cómo evitar eficazmente que otros vean el código fuente del programa Java?

Otros pueden ver fácilmente el código fuente de un programa Java. Siempre que haya un descompilador, cualquiera puede analizar el código de otras personas. Este artículo analiza cómo proteger el código fuente mediante tecnología de cifrado sin modificarlo. el programa original.

¿Por qué cifrar?

Para lenguajes tradicionales como C o C++, es muy fácil proteger el código fuente en la Web. Es una pena no publicarlo. Es fácil para otros echar un vistazo al código fuente de un programa Java. Siempre que haya un descompilador, cualquiera puede analizar el código de otras personas. La flexibilidad de Java hace que el código fuente sea fácil de robar, pero al mismo tiempo. también dificulta la protección del código mediante cifrado. Es relativamente fácil. Lo único que necesitamos saber es el objeto ClassLoader de Java. Por supuesto, el conocimiento sobre Java Cryptography Extension (JCE) también es esencial durante el proceso de cifrado. >

Existen varias técnicas que pueden ocultar los archivos de clase Java para que puedan ser descompilados. Sin embargo, no es difícil modificar el descompilador para procesar estos archivos de clase ofuscados, por lo que no podemos confiar simplemente en la tecnología de ofuscación para garantizar la seguridad. del código fuente

Podemos utilizar herramientas de cifrado populares para cifrar aplicaciones como PGP (Pretty Good Privacy) o GPG (GNU Privacy Guard). En este momento, el usuario final debe descifrar la aplicación antes de ejecutarla. pero después del descifrado, el usuario final tendrá un archivo de clase sin cifrar. Esto no es diferente sin cifrado previo

El mecanismo de carga del código de bytes en tiempo de ejecución en Java significa implícitamente que el código de bytes se puede modificar cada uno. Cada vez que la JVM carga un archivo de clase, necesita un programa llamado ClassLoader. Objeto. Este objeto es responsable de cargar nuevas clases en la JVM en ejecución. La JVM le da a ClassLoader una cadena que contiene el nombre de la clase que se va a cargar (como Java Lang Object). ). Luego, ClassLoader es responsable de encontrar el archivo de clase, cargar los datos originales y convertirlos en un objeto de clase.

Podemos modificar el archivo de clase antes de que se ejecute personalizando el ClassLoader. Esta tecnología se usa ampliamente. Su propósito aquí es hacerlo cuando se carga el archivo de clase. Por lo tanto, el descifrado puede verse como un descifrador sobre la marcha, ya que el archivo de código de bytes descifrado nunca se guarda en el sistema de archivos. es difícil para el ladrón obtener el código descifrado

Debido a la conversión del código de bytes original, el sistema maneja completamente el proceso de convertirse en un objeto Class, por lo que no es difícil crear un ClassLoader personalizado objeto Solo necesita obtener los datos originales primero y luego puede realizar cualquier conversión, incluido el descifrado.

Java simplifica la personalización hasta cierto punto. ClassLoader está integrado en Java. La implementación predeterminada de loadClass sigue siendo responsable. para manejar todos los pasos necesarios, pero también llama a un nuevo método findClass para tener en cuenta varios procesos de carga de clases personalizados.

Esto nos permite escribir ClassLoader personalizado y proporciona un acceso directo para reducir los problemas. Simplemente sobrescriba. findClass en lugar de loadClass Este método evita repetir todos los pasos comunes que el cargador debe realizar porque loadClass es responsable de todo esto.

Sin embargo, el propósito de este artículo es la razón por la cual el ClassLoader personalizado no usa esto. El método es muy simple. Si el ClassLoader predeterminado busca primero el archivo de clase cifrado, puede encontrarlo, pero debido a que el archivo de clase ha sido cifrado, no reconocerá este archivo de clase. El proceso de carga fallará, por lo que debemos hacerlo. La implementación de loadClass aumenta ligeramente la carga de trabajo

Dos cargadores de clases personalizados

Cada JVM en ejecución ya tiene un ClassLoader. Este ClassLoader predeterminado se instala localmente según el valor de la variable de entorno CLASSPATH. Encuentre el archivo de código de bytes apropiado en el sistema de archivos <

/p>

Aplicar un ClassLoader personalizado requiere una comprensión más profunda del proceso. Primero debemos crear una instancia de la clase ClassLoader personalizado y luego pedirle explícitamente que cargue otra clase. Esto obliga a la JVM a cargar esa clase y todas sus clases. contenidos Las clases requeridas están asociadas con un listado de ClassLoader personalizado que muestra cómo cargar archivos de clases con un ClassLoader personalizado

Listado Cargando archivos de clases con un ClassLoader personalizado

El siguiente es un fragmento de referencia.

// Primero crea un objeto ClassLoader ClassLoader myClassLoader = new myClassLoader(); // Usa un objeto ClassLoader personalizado para cargar el archivo de clase // y conviértelo en un objeto Class myClass = myClassLoader loadClass( mypackage MyClass ); // Finalmente crea una instancia de esta clase Object newInstance = myClass newInstance(); // Tenga en cuenta que todas las demás clases requeridas por MyClass se cargarán automáticamente a través // del ClassLoader personalizado

¿Como se mencionó? Antes, solo el ClassLoader personalizado. Primero debe obtener los datos del archivo de clase y luego pasar el código de bytes al sistema de ejecución, que completará las tareas restantes.

ClassLoader tiene varios métodos importantes al crear un ClassLoader personalizado. ClassLoader, solo necesitamos anular uno de ellos, es decir, loadClass proporciona el código para obtener los datos del archivo de clase original. Este método tiene dos parámetros: el nombre de la clase y un indicador que indica si la JVM requiere analizar el nombre de la clase (. es decir, si cargar clases dependientes al mismo tiempo). Si este indicador es verdadero, solo necesitamos llamar a resolveClass antes de regresar a la JVM

Una implementación simple de Listing ClassLoader loadClass()

El siguiente es un fragmento de referencia

clase pública loadClass( nombre de cadena resolución booleana ) throws ClassNotFoundException { try { // El objeto de clase que queremos crear Class clasz = null // Pasos requeridos Si el la clase ya está en el búfer del sistema // No tenemos que cargarla nuevamente clasz = findLoadedClass( name ); if (clasz != null) Devuelve clasz // La siguiente es la parte personalizada byte classData[] = /* Obtener los datos del código de bytes a través de un determinado método */; if (classData! = null) { // Leímos con éxito los datos del código de bytes y ahora los convertimos en un objeto de clase clasz = defineClass( name classData classData length } // Pasos requeridos If); lo anterior no tiene éxito // Intentamos cargarlo con el ClassLoader predeterminado if (clasz == null) clasz = findSystemClass( name ); // Pasos necesarios: cargar la clase relevante si es necesario if (resolve && clasz != null) resolveClass( clasz ); // Devuelve la clase a la persona que llama return clasz;} catch( IOException es decir ) { throw new ClassNotFoundException( es decir toString() );} c

catch( GeneralSecurityException gse ) { throw new ClassNotFoundException( gse toString() }}

El listado muestra una implementación loadClass simple. La mayor parte del código es el mismo para todos los objetos ClassLoader, pero hay una pequeña. Algunas partes (marcadas por comentarios) son únicas. El objeto ClassLoader utiliza varios otros métodos auxiliares durante el procesamiento.

findLoadedClass se usa para verificar que la clase solicitada no existe actualmente. El método loadClass debe llamarlo primero.

defineClass llama a defineClass después de obtener los datos del código de bytes del archivo de clase original para convertirlo en un objeto de clase. Cualquier implementación de loadClass debe llamar a este método.

findSystemClass proporciona soporte para el ClassLoader predeterminado si es. El método personalizado utilizado para encontrar una clase no puede encontrar la clase especificada (o el método personalizado no se utiliza intencionalmente), puede llamar a este método para probar el método de carga predeterminado. Esto es útil especialmente cuando se cargan clases Java estándar desde archivos JAR normales. p>

resolveClass Cuando la JVM desea cargar no solo la clase especificada sino también todas las demás clases a las que hace referencia la clase, establecerá el parámetro de resolución de loadClass en verdadero. En este momento debemos devolver la llamada resolveClass recién cargada antes. el objeto Class se entrega a la persona que llama

Tres cifrados y descifrados

La extensión de cifrado Java es Java Cryptography Extension, conocida como JCE. Es el software de servicio de cifrado de Sun que incluye cifrado y clave. funciones de generación JCE Es una extensión de JCA (Java Cryptography Architecture)

JCE no estipula un algoritmo de cifrado específico, pero proporciona un marco. La implementación específica del algoritmo de cifrado se puede agregar como proveedor de servicios. Además del marco JCE, el paquete de software JCE también contiene el proveedor de servicios SunJCE, que incluye muchos algoritmos de cifrado útiles como DES (Estándar de cifrado de datos) y Blowfish

Para simplificar, en este artículo usaremos el Algoritmo DES para cifrar y descifrar código de bytes. A continuación se utilizan los pasos básicos de JCE que se deben seguir para cifrar y descifrar datos.

Pasos para generar una clave de seguridad Antes de poder cifrar o descifrar cualquier dato, necesita una clave. Una clave es una pequeña porción de datos que se distribuye con la aplicación que se está cifrando. Visualización de listado Listado de cómo generar una clave Generar una clave

El siguiente es un fragmento de referencia

. // El algoritmo DES requiere una fuente de números aleatorios confiable SecureRandom sr = new SecureRandom(); // Genera un objeto KeyGenerator para el algoritmo DES que seleccionamos KeyGenerator kg = KeyGenerator getInstance( DES ); clave secreta SecretKey key = kg generateKey(); // Obtenga el byte de datos de la clave rawKeyData[] = key getEncoded() /* Luego puede usar la clave para cifrarla o descifrarla o guardarla como un archivo para su uso posterior */ doSomething (rawKeyData); paso para cifrar los datos y obtener la clave, luego puede usarlo para cifrar los datos Además del ClassLoader descifrado, generalmente hay un programa separado que cifra la aplicación que se publicará (consulte el Listado) El listado cifra el datos originales con una clave <

/p>

El siguiente es un fragmento de referencia

// El algoritmo DES requiere una fuente de números aleatorios confiable SecureRandom sr = new SecureRandom() byte rawKeyData[] = /* Obtenido mediante algún método Datos clave */; // Crear un objeto DESKeySpec a partir de datos clave sin procesar DESKeySpec dks = new DESKeySpec( rawKeyData ); // Crear una fábrica de claves y utilizarla para convertir DESKeySpec en // un objeto SecretKey SecretKeyFactory keyFactory = SecretKeyFactory getInstance( DES );SecretKey key = keyFactory generateSecret( dks ); // El objeto Cipher realmente completa la operación de cifrado Cipher cipher = Cipher getInstance( DES ); // Inicializa el objeto Cipher con la clave cipher init( Cipher ENCRYPT_MODE key sr ); Datos y cifrado de bytes de datos[] = /* Utilice un método determinado para obtener los datos*/ //Realice formalmente la operación de cifrado byte encryptedData[] = cipher doFinal( data ); // Procese aún más los datos cifrados doSomething( encryptedData ); Pasos para descifrar Cuando los datos se ejecutan en una aplicación cifrada, ClassLoader analiza y descifra el archivo de clase. Los pasos de la operación se muestran en el Listado que utiliza la clave para descifrar los datos

//El algoritmo DES requiere. una fuente de número aleatorio confiable SecureRandom sr = new SecureRandom (); byte rawKeyData[] = /* Usar algún método para obtener los datos clave originales */ // Crear un objeto DESKeySpec a partir de los datos clave originales DESKeySpec dks = new DESKeySpec( rawKeyData) ); // Cree una fábrica de claves y luego úsela para convertir el objeto DESKeySpec en // un objeto SecretKey SecretKeyFactory keyFactory = SecretKeyFactory getInstance( DES ); SecretKey key = keyFactory generateSecret( dks ); operación Cipher cipher = Cipher getInstance( DES ); // Usar inicialización de clave Cipher object cipher init( Cipher DECRYPT_MODE key sr ); // Ahora obtenga los datos y descifre el byte encryptedData[] = /* Obtenga los datos cifrados */ // Formal ejecución del byte de operación de descifrado decryptedData[] = cipher doFinal( encryptedData ); // Procesa aún más los datos descifrados doSomething( decryptedData );

Cuatro ejemplos de aplicación

Hemos presentado cómo cifrar y descifrar datos para implementar una aplicación cifrada, los pasos son los siguientes

Pasos para crear una aplicación Nuestro ejemplo contiene una clase principal de aplicación y dos clases auxiliares (llamadas Foo y Bar respectivamente). No hay ninguna función práctica, pero siempre que podamos cifrar esta aplicación y cifrar otras aplicaciones, no hay problema

<.

p>Pasos para generar una clave de seguridad use la herramienta GenerateKey en la línea de comando (ver GenerateKey java) y escriba la clave en un archivo% java GenerateKey key data

Pasos para aplicar cifrado usando la herramienta EncryptClasses en la línea de comando línea de comando (ver EncryptClasses java) Clase de aplicación de cifrado % java Datos clave de EncryptClasses Clase de aplicación Clase Foo Clase de barra

Este comando reemplaza cada archivo de clase con su respectiva versión cifrada

Pasos para ejecutar el archivo cifrado El usuario de la aplicación ejecuta la aplicación cifrada a través de un programa DecryptStart. El programa DecryptStart se muestra en el Listado DecryptStart java inicia el programa de la aplicación cifrada

El siguiente es un fragmento de referencia

. importar java io *; importar java seguridad *; importar java lang reflejar *; importar javax cripto especificación *; los usará para descifrar la clave privada SecretKey; cifrado privado // El constructor establece el objeto requerido para el descifrado public DecryptStart(clave SecretKey) lanza GeneralSecurityException IOException {esta clave = clave algoritmo = DES; (); System err println( [DecryptStart: creando cifrado]); cifrado = Cipher getInstance( algoritmo); cifrado init( Cipher DECRYPT_MODE key sr } // En el proceso principal, necesitamos leer la clave aquí para crear el DecryptStart // instancia, que es nuestro ClassLoader personalizado // Después de configurar el ClassLoader, lo usamos para cargar la instancia de la aplicación // Finalmente, llamamos al método principal de la instancia de la aplicación a través de la API Java Reflection static public void main( String args[] ) throws Exception { String keyFilename = args[ ]; String appName = args[ ]; // Estos son los parámetros pasados ​​a la aplicación misma String realArgs[] = new String[args length]; longitud); // Leer la clave Error del sistema println( [DecryptStart: clave de lectura]); byte rawKey[] = Util readFile( keyFilename dks = new DESKeySpec( rawKeyF);

actory keyFactory = SecretKeyFactory getInstance( DES ); SecretKey key = keyFactory generateSecret( dks ); // Crea un ClassLoader descifrado DecryptStart dr = new DecryptStart( key ); // Crea una instancia de la clase principal de la aplicación // Cárgala a través del ClassLoader System; err println( [DecryptStart: cargando +appName+ ] ); Classz = dr loadClass( appName ); // Finalmente, llame al método main() de la instancia de la aplicación // a través de la API de Reflection // Obtenga una referencia a main(); String proto[ ] = new String[ ]; Class mainArgs[] = { (new String[ ]) getClass() }; Método main = clasz getMethod( main mainArgs // Crea una matriz que contiene los parámetros del método main() Objeto argsArray); [] = { realArgs }; Error del sistema println( [DecryptStart: ejecutando +appName+ main()] ); // Llama a main() main invoke( null argsArray } public Class loadClass( String name boolean resolve ) lanza ClassNotFoundException { try); { // El objeto Clase que queremos crear Class clasz = null; // Pasos requeridos si la clase ya está en el caché del sistema // No tenemos que cargarla nuevamente clasz = findLoadedClass( name ); null) return clasz; // La siguiente es la parte personalizada try { // Leer el byte del archivo de clase cifrado classData[] = Util readFile( name+ class ); if (classData != null) { // Descifrar el byte decryptedClassData[] = cipher doFinal( classData ); // Conviértelo en una clase nuevamente clasz = defineClass( nombre decryptedClassData  decryptedClassData length ); Error del sistema println( [DecryptStart: descifrando clase +nombre+ ] }} catch( FileNotFoundException fnfe ) // Pasos requeridos si lo anterior no funciona // Intentamos cargarlo con el ClassLoader predeterminado if (clasz == null) clasz = findSystemClass( name ); // Pasos necesarios para cargar las clases relevantes si es necesario if (resolve && clasz != null ) resolveClass( clasz ); // Devuelve la clase a la persona que llama return clasz } catch( IOException es decir ) { throw new ClassNo

tFoundException( es decir toString() );} catch( GeneralSecurityException gse ) {throw new ClassNotFoundException( gse toString() ); } } }Para aplicaciones no cifradas, el método de ejecución normal es el siguiente% java App arg arg arg

Para aplicaciones cifradas, el modo de ejecución correspondiente es % java DecryptStart key data App arg arg arg

DecryptStart tiene dos propósitos. Una instancia de DecryptStart es un ClassLoader personalizado que implementa operaciones de descifrado instantáneo. , DecryptStart también contiene un proceso principal que crea una instancia de descifrador y la usa para cargar y ejecutar la aplicación. El código de la aplicación de muestra está contenido en App java, Foo java y Bar Util java es una herramienta de E/S de archivos. Su código completo se utiliza en muchos ejemplos de este artículo. Descárguelo desde el final de este artículo

Cinco cosas a tener en cuenta

Hemos visto que es muy fácil cifrar un Java. aplicación sin modificar el código fuente Sin embargo, no existe un sistema completamente seguro en el mundo. Este artículo El método de cifrado proporciona un cierto grado de protección del código fuente pero es vulnerable a ciertos ataques.

Aunque la aplicación en sí sí lo es. cifrado, el iniciador DecryptStart no está cifrado. Los atacantes pueden descompilar el iniciador y modificarlo. Guarda los archivos de clase descifrados en el disco. Una forma de reducir este riesgo es realizar una ofuscación de alta calidad en el programa de inicio. También use código compilado directamente en lenguaje de máquina para que el programa de inicio tenga el formato de archivo ejecutable tradicional. Seguridad

Recuerde también que la mayoría de las JVM son intrínsecamente inseguras y los piratas informáticos astutos pueden modificar la JVM para obtener código descifrado. desde fuera del ClassLoader y guárdelo en el disco para evitar la tecnología de cifrado. En este artículo, Java no proporciona un remedio verdaderamente eficaz para este lishixinzhi/Article/program/Java/hx/201311/25751

.