P: Si cifro mis archivos .class y uso un cargador de clases personalizado para cargarlos y descifrarlos sobre la marcha, ¿evitará la descompilación?
R: El problema de prevenir la descompilación del código de bytes de Java es casi tan antiguo como el lenguaje mismo. A pesar de la variedad de herramientas de ofuscación disponibles en el mercado, los programadores Java novatos continúan pensando en formas nuevas e inteligentes de proteger su propiedad intelectual. En esta entrega de Java Q&A, disiparé algunos mitos sobre una idea que se repite con frecuencia en los foros de discusión.
La extrema facilidad con la que los archivos .class de Java se pueden reconstruir en fuentes de Java que se parecen mucho a los originales tiene mucho que ver con los objetivos y compensaciones del diseño de códigos de bytes de Java. Entre otras cosas, el código de bytes de Java fue diseñado para ser compacto, independencia de plataforma, movilidad de red y facilidad de análisis por intérpretes de código de bytes y compiladores dinámicos JIT (just-in-time) / HotSpot. Podría decirse que los archivos .class compilados expresan la intención del programador con tanta claridad que podrían ser más fáciles de analizar que el código fuente original.
Se pueden hacer varias cosas, si no para evitar la descompilación por completo, al menos para hacerlo más difícil. Por ejemplo, como paso posterior a la compilación, podría aplicar un masaje a los datos .class para hacer que el código de bytes sea más difícil de leer cuando se descompila o más difícil de descompilar en un código Java válido (o ambos). Técnicas como realizar una sobrecarga extrema de nombres de métodos funcionan bien para el primero y manipular el flujo de control para crear estructuras de control que no se pueden representar a través de la sintaxis de Java funcionan bien para el segundo. Los ofuscadores comerciales más exitosos utilizan una combinación de estas y otras técnicas.
Desafortunadamente, ambos enfoques deben cambiar el código que ejecutará la JVM, y muchos usuarios temen (con razón) que esta transformación pueda agregar nuevos errores a sus aplicaciones. Además, el cambio de nombre de métodos y campos puede hacer que las llamadas de reflexión dejen de funcionar. Cambiar los nombres de paquetes y clases reales puede romper varias otras API de Java (JNDI (Interfaz de directorio y nombres de Java), proveedores de URL, etc.). Además de los nombres alterados, si se altera la asociación entre las compensaciones de código de bytes de clase y los números de línea de origen, la recuperación de los seguimientos originales de la pila de excepciones podría resultar difícil.
Luego está la opción de ofuscar el código fuente original de Java. Pero fundamentalmente esto causa un conjunto similar de problemas. ¿Encriptar, no ofuscar?
Tal vez lo anterior te haya hecho pensar: "Bueno, ¿y si en lugar de manipular el código de bytes cifro todas mis clases después de la compilación y las descifro sobre la marcha dentro de la JVM (lo que se puede hacer con un cargador de clases personalizado)? Entonces la JVM ejecuta mi código de bytes original y, sin embargo, no hay nada que descompilar o realizar ingeniería inversa, ¿verdad? "
Desafortunadamente, estaría equivocado, tanto al pensar que fue el primero en tener esta idea como al pensar que realmente funciona. Y la razón no tiene nada que ver con la solidez de su esquema de cifrado.