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


3422

En Swing, el campo de contraseña tiene un método getPassword()(devoluciones char[]) en lugar del método habitual getText()(devoluciones String). Del mismo modo, he encontrado una sugerencia de no usar Stringpara manejar contraseñas.

¿Por qué Stringrepresenta una amenaza para la seguridad cuando se trata de contraseñas? Se siente incómodo de usar char[].

Respuestas:


4290

Las cuerdas son inmutables . Eso significa que una vez que haya creado el String, si otro proceso puede volcar la memoria, no hay forma (aparte de la reflexión ) de que pueda 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 ella. Puede sobrescribir la matriz con lo que desee, y la contraseña no estará presente en ninguna parte del sistema, incluso antes de la recolección de basura.

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

Como se señaló en los comentarios, es posible que las matrices que mueve el recolector de basura dejarán copias perdidas 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 el tiempo durante el cual char[]contiene los personajes reales como una ventana de ataque.


3
Si un proceso tiene acceso a la memoria de su aplicación, eso ya es una violación de seguridad, ¿verdad?
Yeti

55
@Yeti: Sí, pero no es como si fuera blanco y negro. Si solo pueden obtener una instantánea de la memoria, entonces desea reducir cuánto daño puede causar esa instantánea, o reducir la ventana durante la cual se puede tomar una instantánea realmente seria.
Jon Skeet

11
Un método de ataque común es ejecutar un proceso que asigne mucha memoria y luego escanee en busca de datos útiles y sobrantes como contraseñas. El proceso no necesita ningún acceso mágico al espacio de memoria de otro proceso; simplemente se basa en la muerte de otros procesos sin borrar primero los datos confidenciales y el sistema operativo tampoco borra la memoria (o los búferes de página) antes de que esté disponible para un nuevo proceso. Borrar las contraseñas almacenadas en char[]ubicaciones corta esa línea de ataque, algo que no es posible cuando se usa String.
Ted Hopp

Si el sistema operativo no borra la memoria antes de pasarlo a otro proceso, ¡el sistema operativo tiene problemas de seguridad importantes! Sin embargo, técnicamente la limpieza a menudo se realiza con trucos de modo protegido y si la CPU está rota (por ejemplo, Intel Meldown) aún es posible leer el contenido de la memoria anterior.
Mikko Rantalainen

1222

Si bien otras sugerencias aquí parecen válidas, hay otra buena razón. Con plain Stringtiene muchas más posibilidades de imprimir accidentalmente la contraseña en registros , monitores u 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: [C@5829428e

40
@voo, pero dudo que inicie sesión mediante escritura directa para transmitir y concatenar. el marco de registro transformaría el char [] en una buena salida
bestsss

41
@ Thr4wn La implementación predeterminada de toStringes classname@hashcode. [Crepresenta char[], el resto es código hash hexadecimal.
Konrad Garus el

15
Idea interesante. Me gustaría señalar que esto no se transpone a Scala, que tiene un toString significativo para las matrices.
mauhiz

37
Escribiría un Passwordtipo de clase para esto. Es menos oscuro y más difícil pasar accidentalmente a algún lado.
derecha el

8
¿Por qué alguien asumiría que la matriz de caracteres se lanzaría como un Objeto? No estoy seguro de entender por qué a todos les gusta esta respuesta. Supongamos que hizo esto: System.out.println ("Contraseña" .toCharArray ());
GC_

680

Para citar un documento oficial, la guía de Java Cryptography Architecture dice lo siguiente acerca char[]vs. Stringcontraseñas (sobre la encriptación basada en contraseña, pero esto es más general acerca de las contraseñas, por supuesto):

Parece lógico recopilar y almacenar la contraseña en un objeto de tipo java.lang.String. Sin embargo, estas son las advertencias: Objectlos tipos Stringson inmutables, es decir, no hay métodos definidos que le permitan cambiar (sobrescribir) o poner a cero el contenido de un String uso posterior. Esta característica hace que los Stringobjetos no sean adecuados para almacenar información confidencial de seguridad, como las contraseñas de los usuarios. En su lugar, siempre debe recopilar y almacenar información confidencial de seguridad en una charmatriz.

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 confidencial

Parte de la información, como los números de Seguro Social (SSN) y las contraseñas, es altamente confidencial. Esta información no debe conservarse durante más tiempo del necesario ni donde pueda verse, incluso por los administradores. Por ejemplo, no debe enviarse a archivos de registro y su presencia no debe ser detectable a través de búsquedas. Algunos datos transitorios pueden guardarse en estructuras de datos mutables, como las matrices de caracteres, y borrarse inmediatamente después de su uso. La eliminación de 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 forma 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 un conocimiento semántico de los datos con los 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.


44
Esta es exactamente la referencia defectuosa / falsa de la que hablo debajo de la respuesta de Jon, es una fuente bien conocida con muchas críticas.
bestsss

37
@bestass, ¿puedes citar también una referencia?
user961954

10
@bestass lo siento, pero Stringse entiende bastante bien y cómo se comporta en la JVM ... hay buenas razones para usar char[]en lugar de Stringcuando se trata de contraseñas de manera segura.
SnakeDoc

3
una vez más, aunque la contraseña se pasa como una cadena desde el navegador a la solicitud como 'cadena' no char? así que no importa lo que hagas, es una cadena, ¿en qué punto debe actuarse y descartarse, nunca almacenarse en la memoria?
Dawesi

3
@Dawesi: At which pointes una aplicación específica, pero la regla general es hacerlo tan pronto como obtenga algo que se supone que es una contraseña (texto sin formato o de otro modo). Lo obtiene del navegador como parte de una solicitud HTTP, por ejemplo. No puede controlar la entrega, pero puede controlar su propio almacenamiento, así que tan pronto como lo reciba, colóquelo en un char [], haga lo que necesite hacer con él, luego configure todo en '0' y deje que el gc reclamarlo.
luis.espinal

354

Las matrices de caracteres ( char[]) se pueden borrar después del uso al establecer 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 usan cadenas, pero si char[]se usa, después de purgar los datos con 0, la contraseña es segura.


13
No es seguro por defecto. Si estamos hablando de una aplicación web, la mayoría de los contenedores web pasarán la contraseña al HttpServletRequestobjeto en texto sin formato. Si la versión de JVM es 1.6 o inferior, estará en el espacio permgen. Si está en 1.7, seguirá siendo legible hasta que se recoja. (Siempre que sea así)
avgvstvs

44
@avgvstvs: las cadenas no se mueven automáticamente al espacio permgen, eso solo se aplica a las cadenas internadas. Además de eso, el espacio permgen también está sujeto a la recolección de basura, solo a una tasa menor. El verdadero problema con el espacio permgen es su tamaño fijo, que es exactamente la razón por la que nadie debería recurrir intern()a cadenas arbitrarias sin pensar . Pero tiene razón en que las Stringinstancias existen en primer lugar (hasta que se recopilan) y convertirlas en char[]matrices después no lo cambia.
Holger

3
@Holger ver docs.oracle.com/javase/specs/jvms/se6/html/… "De lo contrario, se crea una nueva instancia de clase String que contiene la secuencia de caracteres Unicode dada por la estructura CONSTANT_String_info; esa instancia de clase es el resultado de derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String ". En 1.6, la JVM llamaría a un interno por ti cuando detectara secuencias idénticas.
avgvstvs

3
@Holger, tienes razón, combiné el grupo constante y el grupo de cadenas, pero también es falso que el espacio permgen solo se aplica a las cadenas internas. Antes de 1.7, tanto el constant_pool como el string_pool residían en el espacio permgen. Eso significa que la única clase de cadenas que se asignaron al montón fueron como usted dijo, new String()o StringBuilder.toString() administré aplicaciones con muchas constantes de cadena, y como resultado tuvimos una gran cantidad de permgen. Hasta 1.7.
avgvstvs

55
@avgvstvs: bueno, las constantes de cadena son, como manda JLS, siempre internadas, de ahí la afirmación de que las cadenas internas terminaban en el espacio permgen, aplicadas a las constantes de cadena implícitamente. La única diferencia es que las constantes de cadena se crearon en el espacio permgen en primer lugar, mientras que llamar intern()a una cadena arbitraria podría causar la asignación de una cadena equivalente en el espacio permgen. Este último podría obtener GC'ed, si no hubiera una cadena literal de los mismos contenidos compartiendo ese objeto ...
Holger

220

Algunas personas creen que tiene que sobrescribir la memoria utilizada para almacenar la contraseña una vez que ya no la necesita. Esto reduce el tiempo que un atacante tiene que leer la contraseña de su sistema e ignora por completo el hecho de que el atacante ya necesita acceso suficiente para secuestrar la memoria JVM para hacer esto. Un atacante con tanto acceso puede atrapar tus eventos clave y hacer que esto sea completamente inútil (AFAIK, así que corrígeme si me equivoco).

Actualizar

Gracias a los comentarios tengo que actualizar mi respuesta. Aparentemente, hay dos casos en los que esto puede agregar una mejora de seguridad (muy) menor, ya que reduce el tiempo que una contraseña podría aterrizar en el disco duro. Aún así, creo que es excesivo para la mayoría de los casos de uso.

  • Su sistema de destino puede estar mal configurado o debe asumir que lo es y debe ser paranoico con respecto a los volcados del núcleo (puede ser válido si los sistemas no son administrados por un administrador).
  • Su software debe ser demasiado paranoico para evitar fugas de datos con el atacante obteniendo acceso al hardware, utilizando cosas como TrueCrypt (descontinuado), VeraCrypt o CipherShed .

Si es posible, deshabilitar los volcados del núcleo y el archivo de intercambio se encargará de ambos problemas. Sin embargo, requerirían derechos de administrador y podrían reducir la funcionalidad (menos memoria para usar) y extraer RAM de un sistema en ejecución seguiría siendo una preocupación válida.


33
Reemplazaría "completamente inútil" por "solo una pequeña mejora de seguridad". Por ejemplo, podría obtener acceso a un volcado de memoria si tiene acceso de lectura a un directorio tmp, una máquina mal configurada y un bloqueo en su aplicación. En ese caso, no podría instalar un keylogger, pero podría analizar el volcado del núcleo.
Joachim Sauer

44
Limpiar los datos sin cifrar de la memoria tan pronto como haya terminado con ellos se considera una práctica recomendada, no porque sea infalible (no lo es); pero porque reduce tu nivel de exposición a amenazas. Los ataques en tiempo real no se evitan haciendo esto; pero debido a que sirve como una herramienta de mitigación de daños al reducir significativamente la cantidad de datos que están expuestos en un ataque retroactivo en una instantánea de memoria (por ejemplo, una copia de la memoria de sus aplicaciones que se escribió en un archivo de intercambio, o que se leyó de la memoria arrancada desde un servidor en ejecución y movido a otro diferente antes de que su estado fallara).
Dan Is Fiddling By Firelight

99
Tiendo a estar de acuerdo con la actitud de esta respuesta. Me atrevería a proponer que la mayoría de las infracciones de seguridad ocurren en un nivel de abstracción mucho más alto que los bits en la memoria. Claro, probablemente hay escenarios en sistemas de defensa hiper-seguros donde esto puede ser una preocupación considerable, pero pensar seriamente en este nivel es excesivo para el 99% de las aplicaciones donde se está aprovechando .NET o Java (en lo que se refiere a la recolección de basura).
kingdango

10
Después de la penetración Heartbleed de la memoria del servidor, revelando las contraseñas, reemplazaría la cadena "solo una mejora de seguridad menor" con "absolutamente esencial para no usar String para las contraseñas, sino usar un char [] en su lugar".
Peter vdL

99
@PetervdL heartbleed solo permitió la lectura de una colección específica de buffers reutilizados (utilizados tanto para datos críticos de seguridad como para E / S de red sin borrar en el medio, por razones de rendimiento), no puede combinar esto con una Cadena Java ya que, por diseño, no son reutilizables . Tampoco puedes leer en memoria aleatoria usando Java para llegar al contenido de una cadena. Los problemas de lenguaje y diseño que conducen a heartbleed simplemente no son posibles con Java Strings.
josefx

86

No creo que sea una sugerencia válida, pero al menos puedo adivinar el motivo.

Creo que la motivación es querer asegurarse de que puede borrar todo rastro de la contraseña en la memoria de manera rápida y segura 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 puede editar el valor interno de Stringesa manera.

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


44
No diría que el problema es que sus referencias "escaparán" o no. Es solo que las cadenas permanecerán sin modificaciones en la memoria por un tiempo adicional, mientras que char[]pueden modificarse, y luego es irrelevante si se recopilan o no. Y dado que el internamiento de cadenas debe hacerse explícitamente para los no literales, es lo mismo que decir que un char[]campo estático puede hacer referencia a a.
Groo

1
¿No está la contraseña en la memoria como una cadena de la publicación del formulario?
Dawesi

66

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 por char[]todas partes (lo que, por supuesto, es algo bueno), otros datos críticos de 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, utilizándola para realizar alguna operación. Ahora, en este caso, solo rastrear la contraseña no lo ayudaría siempre y cuando el acceso físico al archivo de clave esté restringido adecuadamente. Como atacante, estaría mucho mejor si obtuviera la clave directamente en lugar de la contraseña. La información deseada puede ser filtrada, volcados de núcleo, una sesión de depurador o archivos de intercambio son solo algunos ejemplos.

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

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

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


Una forma de evitar esto es usar una implementación PrivateKeyque en realidad no cargue su contenido privado en la memoria: a través de un token de hardware PKCS # 11, por ejemplo. Quizás una implementación de software de PKCS # 11 podría encargarse de limpiar la memoria manualmente. Quizás sea mejor usar algo como la tienda NSS (que comparte la mayor parte de su implementación con el PKCS11tipo de tienda en Java). El KeychainStore(almacén de claves OSX) carga el contenido completo de la clave privada en sus PrivateKeyinstancias, pero no debería ser necesario. (No estoy seguro de lo que WINDOWS-MYhace KeyStore en Windows).
Bruno

@Bruno Claro, los tokens basados ​​en hardware no sufren de esto, pero ¿qué pasa con las situaciones en las que está más o menos obligado a usar una clave de software? No todas las implementaciones tienen el presupuesto para pagar un HSM. Las tiendas de claves de software en algún momento tendrán que cargar la clave en la memoria, por lo que en mi opinión, al menos deberíamos tener la opción de borrar la memoria a voluntad nuevamente.
relieve

Absolutamente, me preguntaba si algunas implementaciones de software equivalentes a un HSM podrían comportarse mejor en términos de limpieza de la memoria. Por ejemplo, cuando se usa la autenticación de cliente con Safari / OSX, el proceso de Safari nunca ve la clave privada, la biblioteca SSL subyacente proporcionada por el sistema operativo se comunica directamente con el demonio de seguridad que solicita al usuario que use la clave del llavero. Si bien todo esto se hace en software, una separación similar como esta puede ayudar si la firma se delega a una entidad diferente (incluso basada en software) que descargaría o borraría mejor la memoria.
Bruno

@Bruno: idea interesante, una capa de indirección adicional que se encarga de limpiar la memoria resolvería esto de manera transparente. ¿Escribir un contenedor PKCS # 11 para el almacén de claves de software ya podría hacer el truco?
relieve

51

Como dice Jon Skeet, no hay manera, excepto mediante el uso de la reflexión.

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

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 corre

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

Nota: si el carácter de String [] 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 procesar del proceso, podría verla. En general, debe evitar que alguien tenga dicho acceso.


2
Es mejor hacer también algo para evitar imprimir la longitud de la contraseña que obtenemos '***********'.
chux - Restablece a Mónica el

@chux puede usar un carácter de ancho cero, aunque esto puede ser más confuso que útil. No es posible cambiar la longitud de una matriz de caracteres sin usar Inseguro. ;)
Peter Lawrey

55
Debido a la deduplicación de cadenas de Java 8, creo que hacer algo así puede ser bastante destructivo ... puede terminar borrando otras cadenas en su programa que por casualidad tenían el mismo valor de la cadena de contraseña. Improbable, pero posible ...
jamp

1
@PeterLawrey Debe habilitarse con un argumento JVM, pero está ahí. Puede leer sobre esto aquí: blog.codecentric.de/en/2014/08/…
jamp

1
Existe una alta probabilidad de que la contraseña todavía esté en Scannerel búfer interno y, como no la usó System.console().readPassword(), en forma legible en la ventana de la consola. Pero para la mayoría de los casos de uso práctico, la duración de usePasswordla ejecución es el problema real. Por ejemplo, cuando se conecta a otra máquina, lleva un tiempo considerable y le dice al atacante que es el momento adecuado para buscar la contraseña en el montón. La única solución es evitar que los atacantes lean la memoria del montón ...
Holger

42

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

1. Dado que los Strings son inmutables en Java, si almacena la contraseña como texto sin formato, estará disponible en la memoria hasta que el recolector de basura lo borre, y dado que String se usa en el conjunto de String para su reutilización, existe una posibilidad bastante alta de que lo haga permanecer en la memoria durante un período prolongado, lo que representa 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 en lugar de texto sin formato. Dado que las cadenas son inmutables, no hay forma de que el contenido de las cadenas se pueda cambiar porque cualquier cambio producirá una nueva cadena, mientras que si usa un carácter [] aún puede establecer todos los elementos en blanco o cero. Por lo tanto, almacenar una contraseña en una matriz de caracteres mitiga claramente el riesgo de seguridad de robar una contraseña.

2. Java mismo recomienda usar el método getPassword () de JPasswordField que devuelve un char [], en lugar del método obsoleto getText () que devuelve las contraseñas en texto claro indicando razones de seguridad. Es bueno seguir los consejos del equipo de Java y adherirse a los estándares en lugar de ir en contra de ellos.

3. Con String siempre existe el riesgo de imprimir texto sin formato en un archivo de registro o consola, pero si usa una matriz, no imprimirá el contenido de una matriz, sino que se imprimirá su ubicación de memoria. Aunque no es una razón real, 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: [C@110b053

Referenciado desde este blog . Espero que esto ayude.


10
Esto es redundante. Esta respuesta es una versión exacta de la respuesta escrita por @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . No copie, pegue ni duplique la misma respuesta dos veces.
Lucky

¿Cuál es la diferencia entre 1.) System.out.println ("Contraseña de personaje:" + charPassword); y 2.) System.out.println (charPassword); Porque está dando el mismo "desconocido" como salida.
Vaibhav_Sharma

3
@Lucky Lamentablemente, la respuesta anterior a la que se vinculó se plagió del mismo blog que esta respuesta, y ahora se ha eliminado. Ver meta.stackoverflow.com/questions/389144/… . Esta respuesta simplemente ha cortado y pegado del mismo blog y no ha agregado nada, por lo que simplemente debería haber sido un comentario que vincula a la fuente original.
skomisa

41

Editar: 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 realmente compararía las contraseñas de texto sin formato. Por favor no lo hagas. Use un hash unidireccional seguro con sal y un número razonable de iteraciones . Considere 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 sincronización? Puede ser poco probable, pero teóricamente podría cronometrar la comparación de 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 sobre ataques de tiempo:


Pero eso también podría estar allí en la comparación de char [], en algún lugar también estaríamos haciendo lo mismo en la validación de contraseña. Entonces, ¿cómo es char [] mejor que string?
Mohit Kanwar

3
Tienes toda la razón, el error se puede cometer de cualquier manera. Conocer el problema es lo más importante aquí, considerando que no hay un método explícito de comparación de contraseñas en Java para las contraseñas basadas en String o char []. Diría que la tentación de usar compare () para Strings es una buena razón para usar char []. De esa manera, al menos tiene control sobre cómo se realiza la comparación (sin extender String, lo cual es una pena).
Graph Theory

Además de comparar contraseñas en texto plano no es lo correcto de todos modos, la tentación de utilizar Arrays.equalspara char[]es tan alto como para String.equals. Si a alguien le importaba, había una clase de clave dedicada que encapsulaba la contraseña real y se ocupaba de los problemas: oh, espera, los paquetes de seguridad reales tienen clases de clave dedicadas, estas preguntas y respuestas son solo un hábito fuera de ellas, por ejemplo JPasswordField, para usar char[]en lugar de String(donde los algoritmos reales usan de byte[]todos modos).
Holger

El software relevante para la seguridad debería hacer algo como sleep(secureRandom.nextInt())antes de rechazar un intento de inicio de sesión de todos modos, eso no solo elimina la posibilidad de ataques de tiempo, sino que también contrarresta los intentos de fuerza bruta.
Holger

36

No hay nada que le dé char array frente a String a menos que lo limpie manualmente después de su uso, y no he visto a nadie realmente haciendo eso. Entonces, para mí, la preferencia de char [] frente a String es un poco exagerada.

Eche un vistazo a la biblioteca Spring Security ampliamente utilizada aquí y pregúntese: ¿son incompetentes los tipos de Spring Security o las contraseñas char [] simplemente no tienen mucho sentido? Cuando un pirata informático desagradable toma volcados de memoria de su RAM, asegúrese de obtener todas las contraseñas, incluso si utiliza formas sofisticadas para ocultarlas.

Sin embargo, Java cambia todo el tiempo, y algunas características aterradoras como la función de deduplicación de cadenas de Java 8 pueden internar objetos de cadenas sin su conocimiento. Pero esa es una conversación diferente.


¿Por qué da miedo la deduplicación de cadenas? Solo se aplica cuando hay al menos dos cadenas que tienen el mismo contenido, entonces, ¿qué peligro surgiría si permitimos que estas dos cadenas ya idénticas compartan la misma matriz? O preguntemos al revés: si no hay deduplicación de cadenas, ¿qué ventaja surge del hecho de que ambas cadenas tienen una matriz distinta (del mismo contenido)? En cualquier caso, habrá una variedad de ese contenido vivo al menos mientras la Cadena de ese contenido viva más larga esté viva ...
Holger

@Holger cualquier cosa que esté fuera de su control es un riesgo potencial ... por ejemplo, si dos usuarios tienen la misma contraseña, esta maravillosa característica los almacenará a ambos en un solo carácter [], lo que hace evidente que son lo mismo, no estoy seguro si eso es un gran riesgo pero aún así
Oleg Mikheev

Si tiene acceso a la memoria de almacenamiento dinámico y a ambas instancias de cadena, no importa si las cadenas apuntan a la misma matriz o a dos matrices del mismo contenido, cada una es fácil de averiguar. Especialmente, ya que es irrelevante de todos modos. Si está en este punto, toma ambas contraseñas, sean idénticas o no. El error real radica en el uso de contraseñas de texto sin formato en lugar de hashes salados.
Holger

@Holger para verificar la contraseña, debe estar en texto claro en la memoria durante un tiempo, 10 ms, incluso si es solo para crear un hash salado. Entonces, si sucede que hay dos contraseñas idénticas guardadas en la memoria, incluso durante 10 ms, la deduplicación puede entrar en juego. Si realmente pasa las cadenas, se mantienen en la memoria durante mucho más tiempo. El sistema que no se reinicia durante meses recogerá muchos de estos. Solo teorizando.
Oleg Mikheev

Parece que tienes un malentendido fundamental sobre la desduplicación de cadenas. No hace “cadenas internas”, todo lo que hace es dejar que las cadenas con el mismo contenido apunten a la misma matriz, lo que en realidad reduce el número de instancias de matriz que contienen la contraseña de texto sin formato, ya que todas las instancias de matriz menos una pueden reclamarse y sobrescribirse. por otros objetos de inmediato. Estas cadenas todavía se recopilan como cualquier otra cadena. Tal vez sea útil, si comprende que la desduplicación en realidad la realiza el recolector de basura, para cadenas que solo han sobrevivido a múltiples ciclos de GC.
Holger

30

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

Por otro lado, char [] son ​​mutables tan pronto como se realiza la autenticación, puede sobrescribirlos con cualquier carácter como todas las M o barras invertidas. Ahora, incluso si alguien realiza un volcado de almacenamiento dinámico, 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 de borrar el contenido del Objeto usted mismo frente a esperar a que el GC lo haga.


Solo serán GC'd si la JVM en cuestión es> 1.6. Antes de 1.7, todas las cadenas se almacenaban en permgen.
avgvstvs

@avgvstvs: "todas las cadenas se almacenaron en permgen" está totalmente equivocado. Solo las cadenas internadas se almacenaban allí y, si no se originaban a partir de literales de cadena a los que hacía referencia el código, aún se recolectaban basura. Solo piensa en ello. Si las cadenas generalmente no se GCed en JVM antes de 1.7, ¿cómo podría sobrevivir cualquier aplicación Java más de unos pocos minutos?
Holger

@ Holger Esto es falso. Internados stringsY el Stringgrupo (grupo de cadenas usadas previamente) AMBOS se almacenaron en Permgen antes de 1.7. Además, consulte la sección 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… La JVM siempre verificó Stringssi tenían el mismo valor de referencia y lo llamaría String.intern()POR USTED. El resultado fue que cada vez que la JVM detectaba cadenas idénticas en el constant_poolo montón las movía a permgen. Y trabajé en varias aplicaciones con "permgen progresivo" hasta 1.7. Fue un verdadero problema.
avgvstvs

Para recapitular: hasta 1.7, las cadenas comenzaron en el montón, cuando se usaron, se colocaron en el constant_poolpermgen ubicado en IN, y luego, si se usaba una cadena más de una vez, sería internada.
avgvstvs

@avgvstvs: No hay un "grupo de cadenas utilizadas anteriormente". Estás lanzando cosas completamente diferentes juntas. Hay un conjunto de cadenas de tiempo de ejecución que contiene literales de cadena y cadenas explícitamente internados, pero ninguna otra. Y cada clase tiene su grupo constante que contiene constantes de tiempo de compilación . Estas cadenas se agregan automáticamente al grupo de tiempo de ejecución, pero solo estas , no todas las cadenas.
Holger

21

La cadena es inmutable y va al grupo de cadenas. Una vez escrito, no se puede sobrescribir.

char[] es una matriz que debe sobrescribir una vez que haya usado 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) {

Arrays.fill(pass, '0');
}

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 tiene acceso al servidor para fines de monitoreo. Podía echar un vistazo a una caída de emergencia y encontrar las contraseñas.


2
ch = nulo; no puedes hacer esto
Yugerten

¿Pero request.getPassword()ya no crea la cadena y la agrega al grupo?
Tvde1

1
ch = '0'cambia la variable local ch; no tiene efecto en la matriz. Y su ejemplo no tiene sentido de todos modos, comienza con una instancia de cadena a la que llama toCharArray(), creando una nueva matriz e incluso cuando sobrescribe la nueva matriz correctamente, no cambia la instancia de cadena, por lo que no tiene ventaja sobre solo usar la cadena ejemplo.
Holger

@ Holger gracias. Se corrigió el código de limpieza de la matriz de caracteres.
ACV

3
Simplemente puede usarArrays.fill(pass, '0');
Holger

18

La respuesta corta 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 pueden modificarse una vez creados, y por lo tanto, la única forma de eliminar su contenido de la memoria es recolectar basura. Será solo entonces cuando la memoria liberada por el objeto pueda sobrescribirse y los datos se habrán ido.

Ahora la recolección de basura en Java no ocurre en ningún intervalo garantizado. Por Stringlo tanto, 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 algún registro.

Con una matriz de caracteres , puede leer la contraseña, terminar de trabajar con ella tan pronto como sea posible y luego cambiar de inmediato el contenido.


@fallenidol No, en absoluto. Lea atentamente y encontrará las diferencias.
Pritam Banerjee

12

La cadena en Java es inmutable. Por lo tanto, cada vez que se crea una cadena, permanecerá en la memoria hasta que se recolecte basura. Por lo tanto, 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 basura.

Con la matriz de caracteres, el contenido de la matriz se puede modificar o borrar una vez que se cumple el propósito de la contraseña. El contenido original de la matriz no se encontrará en la memoria después de que se modifique 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.


2

Es discutible si debe usar String o Char [] para este propósito porque ambos tienen sus ventajas y desventajas. Depende de lo que el usuario necesite.

Dado que las cadenas en Java son inmutables, cada vez que algunos intentan manipular su cadena, crea un nuevo objeto y la cadena existente no se ve afectada. Esto podría verse como una ventaja para almacenar una contraseña como una cadena, pero el objeto permanece en la memoria incluso después de su uso. Entonces, si alguien obtuvo de alguna manera la ubicación de la memoria del objeto, esa persona puede rastrear fácilmente su contraseña almacenada en esa ubicación.

Char [] es mutable, pero tiene la ventaja de que después de su uso, el programador puede limpiar explícitamente la matriz o anular los valores. Entonces, cuando termina de usarse, se limpia y nadie podría saber sobre la información que había almacenado.

En base a las circunstancias anteriores, uno puede hacerse una idea de si ir con String o ir con Char [] para sus requisitos.


0

Cadena de caja:

    String password = "ill stay in StringPool after Death !!!";
    // some long code goes
    // ...Now I want to remove traces of password
    password = null;
    password = "";
    // above attempts wil change value of password
    // but the actual password can be traced from String pool through memory dump, if not garbage collected

Caso CHAR ARRAY:

    char[] passArray = {'p','a','s','s','w','o','r','d'};
    // some long code goes
    // ...Now I want to remove traces of password
    for (int i=0; i<passArray.length;i++){
        passArray[i] = 'x';
    }
    // Now you ACTUALLY DESTROYED traces of password form memory
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.