Las contraseñas e información sensible en el código fuente o bytecode de Java no son seguras

Escrito por el , actualizado el .
java planeta-codigo programacion seguridad
Comentarios

Java

En Java el código fuente se compila a una representación en bytecode independiente de la arquitectura del procesador y sistema operativo donde posteriormente se ejecuta. Este bytecode es un formato binario pero que puede ser decompilado fácilmente con la herramienta javap incluida en el propio JDK o examinado su contenido simplemente con un editor de texto hexadecimal. Con estas herramientas es fácil ver las instrucciones del programa para la máquina virtual y los caracteres de las cadenas que fueron incluidas en el código fuente.

Lógicamente, de este modo hardcodear una contraseña en el código fuente hace que el código fuente sea inseguro pero es que incluso distribuir el binario compilado no es seguro ya que cualquier usuario que tenga acceso al binario de la aplicación es potencialmente capaz de recuperar la contraseña, tener acceso al binario quizá no sea sencillo pero aparte de eso no hay ninguna medida de seguridad adicional que añada más dificultad. Quien dice contraseña dice igualmente una clave privada de cifrado simétrico usada para cifrar o descifrar datos o un bearer token de OAuth. En definitiva es un problema de seguridad.

Compilado el programa y utilizando la herramienta javap se puede obtener el valor de la contraseña. ¿Adivinas cual es la contraseña en este archivo binario de bytecode examinado el contenido?

Contenido hexadecimal de un archivo binario de bytecode Java y C

El siguiente ejemplo sencillo de un programa Java incluye una cadena con una supuesta contraseña. Se observa que en el archivo visualizado en formato hexadecimal o decompilado los caracteres de la cadena son fácilmente reconocibles.

1
2
3
4
5
6
public class Main {
    public static void main(String[] args) {
        String password = "Mz6K3P9rDZ7G6wH";
        System.out.println("Hello World!");
    }
}

Para compilar este pequeño programa se utiliza el comando javac que genera el archivo de bytecode Main.class.

1
2
$ javac Main.java
$ xxd Main.class

Para decompilar este pequeño programa se utiliza el comando javap, con él se ven las instrucciones interpretadas por la máquina virtual de Java y la cadena con la contraseña.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ javap -c Main.class
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Mz6K3P9rDZ7G6wH
       2: astore_1
       3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #4                  // String Hello World!
       8: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: return
}

Que el contenido de la constante de las cadenas del programa sea incluido en el binario y examinable con un editor hexadecimal no es exclusivo de Java, en otros lenguajes de programación como C y formatos de ejecutables como ELF para Linux se da el mismo caso al examinar el binario como se observa en las imágenes anteriores. Seguramente en la mayoría de lenguajes, como C#, ocurra lo mismo.

1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
    const char *password = "Mz6K3P9rDZ7G6wH";
    printf("Hello World!");
    return 0;
}
1
2
$ gcc Main.c -o Main
$ xxd Main

Una solución para evitar este problema de seguridad es ubicar la contraseña a un archivo de configuración incluso con los valores sensibles cifrados y que sean descifrados únicamente por la aplicación en el momento de iniciarse. En el caso de ubicar este archivo de configuración en un servidor se puede proteger mediante permisos para que solo los administradores o algunos desarrolladores tenga acceso a él y no cualquier usuario que consiga acceso al sistema.