java ¿Por qué se prefiere char[] a String para contraseñas?




security passwords (14)

Mientras que otras sugerencias aquí parecen ser válidas, hay otra buena razón. Con String simple tiene muchas más posibilidades de imprimir accidentalmente la contraseña en registros , monitores o algún otro lugar inseguro. char[] es menos vulnerable.

Considera esto:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Huellas dactilares:

String: Password
Array: [[email protected]

En Swing, el campo de contraseña tiene un getPassword() (devuelve char[] ) en lugar del método usual getText() (devuelve String ). Del mismo modo, me he encontrado con una sugerencia de no utilizar String para manejar contraseñas.

¿Por qué String representa una amenaza para la seguridad cuando se trata de contraseñas? Se siente inconveniente al usar char[] .


La cadena es inmutable y va a la agrupación de cadenas. Una vez escrito, no se puede sobrescribir.

char[] es una matriz que debería sobrescribir una vez que usó la contraseña y así es como debe hacerse:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {
 for (char ch: pass) {
  ch = null;
 }
}

Un escenario en el que el atacante podría usarlo es un crashdump (cuando la JVM se bloquea y genera un volcado de memoria) podrá ver la contraseña.

Eso no es necesariamente un atacante externo malicioso. Este podría ser un usuario de soporte que tenga acceso al servidor para fines de monitoreo. Podía echar un vistazo a un crashdump y encontrar las contraseñas.


Edit: Volviendo a esta respuesta después de un año de investigación de seguridad, me doy cuenta de que tiene la desafortunada implicación de que alguna vez compararía contraseñas de texto simple. Por favor no lo hagas Utilice un hash seguro de una sola vía con una sal y un número razonable de iteraciones . Considera usar una biblioteca: ¡esto es difícil de entender!

Respuesta original: ¿Qué pasa con el hecho de que String.equals () utiliza la evaluación de cortocircuito y, por lo tanto, es vulnerable a un ataque de tiempo? Puede ser improbable, pero teóricamente podría cronometrar la comparación de la contraseña para determinar la secuencia correcta de caracteres.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Algunos recursos más en los ataques de tiempo:


No creo que esta sea una sugerencia válida, pero, al menos puedo adivinar la razón.

Creo que la motivación es querer asegurarse de que pueda borrar todo el seguimiento de la contraseña en la memoria de manera inmediata y con certeza después de su uso. Con un char[] podría sobrescribir cada elemento de la matriz con un espacio en blanco o algo seguro. No puedes editar el valor interno de una String esa manera.

Pero eso solo no es una buena respuesta; ¿por qué no asegurarse de que una referencia al char[] o String no se escape? Entonces no hay problema de seguridad. Pero la cosa es que los objetos String pueden ser intern() en teoría y mantenidos vivos dentro del grupo constante. Supongo que usar char[] prohíbe esta posibilidad.


Estas son todas las razones, uno debe elegir char [] array en lugar de String para la contraseña.

1. Dado que las cadenas son inmutables en Java, si almacena la contraseña como texto sin formato, estará disponible en la memoria hasta que el recolector de basura la borre y, dado que las cadenas se utilizan en el grupo de cadenas para su reutilización, existe una gran posibilidad de que permanezcan en la memoria por mucho tiempo. Duración, que suponen una amenaza para la seguridad. Dado que cualquiera que tenga acceso al volcado de memoria puede encontrar la contraseña en texto sin cifrar, esa es otra razón por la que siempre debe usar una contraseña encriptada que el texto simple. Dado que las cadenas son inmutables, no hay forma de que se puedan cambiar los contenidos de las cadenas porque cualquier cambio producirá una nueva cadena, mientras que si char [] aún puede establecer todo su elemento como blanco o cero. Por lo tanto, almacenar la contraseña en una matriz de caracteres mitiga claramente el riesgo de seguridad de robar la contraseña.

2. Java mismo recomienda usar el método getPassword () de JPasswordField que devuelve un método char [] y getText () en desuso que devuelve la contraseña en texto sin cifrar indicando el motivo de seguridad. Es bueno seguir los consejos del equipo de Java y seguir el estándar en lugar de ir en contra de él.

3. Con String siempre existe el riesgo de imprimir texto sin formato en el archivo de registro o la consola, pero si usa Array, no imprimirá el contenido de la matriz, sino que se imprimirá su ubicación en la memoria. Aunque no es una razón real, pero todavía tiene sentido.

    String strPassword="Unknown";
    char[] charPassword= new char[]{'U','n','k','w','o','n'};
    System.out.println("String password: " + strPassword);
    System.out.println("Character password: " + charPassword);

    String password: Unknown
    Character password: [[email protected]

Referencia de: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html Espero que esto ayude.


Las cuerdas son inmutables . Eso significa que una vez que haya creado la String , si otro proceso puede volcar la memoria, no hay manera (aparte de la reflection ) que puede deshacerse de los datos antes de que comience la recolección de basura .

Con una matriz, puede borrar explícitamente los datos una vez que haya terminado con ellos. Puede sobrescribir la matriz con lo que quiera, y la contraseña no estará presente en ningún lugar del sistema, incluso antes de la recolección de basura.

Así que sí, esto es un problema de seguridad, pero incluso usar char[] solo reduce la ventana de oportunidad para un atacante, y es solo para este tipo de ataque específico.

Como se señaló en los comentarios, es posible que las matrices que se mueven por el recolector de basura dejen copias extraviadas de los datos en la memoria. Creo que esto es específico de la implementación: el recolector de basura puede borrar toda la memoria a medida que avanza, para evitar este tipo de cosas. Incluso si lo hace, todavía hay un tiempo durante el cual el char[] contiene los caracteres reales como una ventana de ataque.


Como dice Jon Skeet, no hay forma de hacerlo excepto mediante la reflexión.

Sin embargo, si la reflexión es una opción para usted, puede hacer esto.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

cuando se ejecuta

please enter a password
hello world
password: '***********'

Nota: si el carácter [] de la cadena se ha copiado como parte de un ciclo de GC, existe la posibilidad de que la copia anterior esté en algún lugar de la memoria.

Esta copia antigua no aparecería en un volcado de almacenamiento dinámico, pero si tiene acceso directo a la memoria sin formato del proceso, podría verlo. En general debes evitar que alguien tenga tal acceso.


Para citar un documento oficial, la guía de Arquitectura de criptografía de Java dice esto sobre las contraseñas de char[] vs. String (sobre el cifrado basado en contraseñas, pero esto es más general sobre las contraseñas, por supuesto):

Parece lógico recopilar y almacenar la contraseña en un objeto de tipo java.lang.String . Sin embargo, aquí está la advertencia: los Object de tipo String son inmutables, es decir, no hay métodos definidos que le permitan cambiar (sobrescribir) o borrar el contenido de una String después de su uso. Esta función hace que los objetos String no sean adecuados para almacenar información sensible a la seguridad, como las contraseñas de los usuarios. En su lugar, siempre debe recopilar y almacenar información confidencial de seguridad en una matriz de caracteres.

La Pauta 2-2 de las Pautas de codificación segura para el lenguaje de programación Java, versión 4.0 también dice algo similar (aunque originalmente se encuentra en el contexto del registro):

Pauta 2-2: No registrar información altamente sensible

Alguna información, como los números de Seguro Social (SSN) y las contraseñas, es altamente confidencial. Esta información no se debe guardar por más tiempo del necesario ni donde pueda verse, ni siquiera por los administradores. Por ejemplo, no debe enviarse a los archivos de registro y su presencia no debe ser detectable a través de búsquedas. Algunos datos transitorios pueden mantenerse en estructuras de datos mutables, como matrices de caracteres, y borrarse inmediatamente después de su uso. La limpieza de las estructuras de datos ha reducido la efectividad en los sistemas de tiempo de ejecución Java típicos, ya que los objetos se mueven en la memoria de manera transparente al programador.

Esta guía también tiene implicaciones para la implementación y el uso de bibliotecas de nivel inferior que no tienen conocimiento semántico de los datos que están tratando. Como ejemplo, una biblioteca de análisis de cadenas de bajo nivel puede registrar el texto en el que trabaja. Una aplicación puede analizar un SSN con la biblioteca. Esto crea una situación en la que los SSN están disponibles para los administradores con acceso a los archivos de registro.


La respuesta breve y directa sería porque char[]es mutable, mientras que los Stringobjetos no lo son.

StringsEn Java son objetos inmutables. Es por eso que no se pueden modificar una vez creados, y por lo tanto, la única forma de que sus contenidos se eliminen de la memoria es haciendo que se recolecten los desechos. Será entonces cuando la memoria liberada por el objeto pueda sobrescribirse y los datos desaparecerán.

Ahora la recolección de basura en Java no ocurre en ningún intervalo garantizado. Por Stringlo tanto, la lata puede permanecer en la memoria durante mucho tiempo, y si un proceso se bloquea durante este tiempo, el contenido de la cadena puede terminar en un volcado de memoria o en algún registro.

Con una matriz de caracteres , puede leer la contraseña, terminar de trabajar con ella tan pronto como pueda y luego cambiar los contenidos de inmediato.


  1. Las cadenas son inmutables en Java si almacena la contraseña como texto sin formato, estará disponible en la memoria hasta que el recolector de basura la borre y, dado que las cadenas se utilizan en el grupo de cadenas para su reutilización, existe una gran posibilidad de que permanezcan en la memoria por mucho tiempo. Duración, lo que plantea una amenaza de seguridad. Dado que cualquier persona que tenga acceso al volcado de memoria puede encontrar la contraseña en texto simple
  2. La recomendación de Java utiliza el método getPassword() de JPasswordField, que devuelve un método char [] y el método getText() desuso, que devuelve la contraseña en texto claro que indica el motivo de seguridad.
  3. aString () siempre existe el riesgo de imprimir texto sin formato en el archivo de registro o la consola, pero si usa Array, no imprimirá el contenido de la matriz, sino que se imprimirá su ubicación de memoria.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    Contraseña de la cadena: contraseña

    Contraseña del personaje: [C @ 110b2345

Consideraciones finales: aunque usar char [] no es suficiente, es necesario borrar el contenido para estar más seguro. También sugiero que trabaje con contraseña cifrada o con hash en lugar de texto sin formato y que la borre de la memoria tan pronto como se complete la autenticación.


La respuesta ya se ha dado, pero me gustaría compartir un problema que descubrí últimamente con las bibliotecas estándar de Java. Si bien ahora tienen mucho cuidado de reemplazar las cadenas de contraseña con char[] todas partes (lo que, por supuesto, es bueno), otros datos críticos para la seguridad parecen pasarse por alto cuando se trata de borrarlos de la memoria.

Estoy pensando, por ejemplo, en la clase PrivateKey . Considere un escenario en el que cargaría una clave RSA privada desde un archivo PKCS # 12, usándola para realizar alguna operación. Ahora, en este caso, el solo hecho de rastrear la contraseña no será de ayuda, siempre y cuando el acceso físico al archivo de clave esté correctamente restringido. Como atacante, estaría mucho mejor si obtuviera la clave directamente en lugar de la contraseña. La información deseada se puede filtrar, varios volcados de núcleo, una sesión de depuración o archivos de intercambio son solo algunos ejemplos.

Y resulta que, no hay nada que le permita borrar la información privada de una PrivateKey de la memoria, porque no hay una API que le permita borrar los bytes que forman la información correspondiente.

Esta es una mala situación, ya que este paper describe cómo esta circunstancia podría ser potencialmente explotada.

La biblioteca OpenSSL, por ejemplo, sobrescribe las secciones de memoria crítica antes de que se liberen las claves privadas. Dado que Java se recolecta en la basura, necesitaríamos métodos explícitos para borrar e invalidar la información privada de las claves de Java, que se aplicarán inmediatamente después de usar la clave.


Las cadenas son inmutables y no se pueden modificar una vez que se han creado. La creación de una contraseña como una cadena dejará referencias extraviadas a la contraseña en el montón o en el conjunto de cadenas. Ahora bien, si alguien toma un montón de volcado del proceso de Java y lo analiza cuidadosamente, podría adivinar las contraseñas. Por supuesto, estas cadenas no utilizadas serán recolectadas en la basura, pero eso depende de cuándo se active el GC.

En el otro lado, los caracteres char [] son ​​mutables tan pronto como se realiza la autenticación, puede sobrescribirlos con cualquier carácter, como todas las M o las barras invertidas. Ahora, incluso si alguien toma un volcado de pila, es posible que no pueda obtener las contraseñas que no están actualmente en uso. Esto le da más control en el sentido como borrar el contenido del Objeto en vez de esperar a que el GC lo haga.


Las matrices de caracteres ( char[] ) se pueden borrar después del uso configurando cada carácter en cero y las cadenas no. Si alguien puede ver de alguna manera la imagen de la memoria, puede ver una contraseña en texto sin formato si se utilizan cadenas, pero si se usa char[] , después de purgar los datos con 0, la contraseña es segura.


La cadena en java es inmutable. Así que cada vez que se crea una cadena, permanecerá en la memoria hasta que se recoja la basura. Así que cualquiera que tenga acceso a la memoria puede leer el valor de la cadena.
Si se modifica el valor de la cadena, terminará creando una nueva cadena. Por lo tanto, tanto el valor original como el valor modificado permanecen en la memoria hasta que se recolecta la basura.

Con la matriz de caracteres, el contenido de la matriz se puede modificar o borrar una vez que se sirve el propósito de la contraseña. El contenido original de la matriz no se encontrará en la memoria después de su modificación e incluso antes de que comience la recolección de basura.

Debido a la preocupación de seguridad, es mejor almacenar la contraseña como una matriz de caracteres.





char