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

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

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

Las cadenas son inmutables . Eso significa que una vez que haya creado el String , si otro proceso puede volcar la memoria, no hay forma de que (aparte de la reflexión ) pueda deshacerse de los datos antes de que se inicie la recolección de elementos no utilizados .

Con una matriz, puede borrar los datos de forma explícita una vez que haya terminado con ellos. Puede sobrescribir la matriz con lo que desee 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 el uso de 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 movidas por 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 un tiempo durante el cual el char[] contiene los caracteres reales como una ventana de ataque.

Mientras que otras sugerencias aquí parecen válidas, hay otra buena razón. Con String simple tiene 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 

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

Parecería lógico recolectar y almacenar la contraseña en un objeto de tipo java.lang.String . Sin embargo, esta es la advertencia: los Object de tipo String son inmutables, es decir, no hay métodos definidos que le permitan cambiar (sobrescribir) o poner a cero 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 recostackr y almacenar información confidencial de seguridad en una matriz char .

La Pauta 2-2 de las Pautas de encoding segura para el Lenguaje de progtwigción Java, Versión 4.0 también dice algo similar (aunque originalmente se encuentra en el contexto del registro):

Pauta 2-2: No registre información altamente confidencial

Parte de la información, como los números de Seguridad Social (SSN) y las contraseñas, es muy delicada. Esta información no se debe guardar por más tiempo de lo necesario ni donde pueda ser visto, ni siquiera por los administradores. Por ejemplo, no debe enviarse a archivos de registro y su presencia no debe poder detectarse mediante búsquedas. Algunos datos transitorios pueden mantenerse en estructuras de datos mutables, como matrices de caracteres, y borrarse inmediatamente después del uso. La eliminación 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 forma transparente para el progtwigdor.

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.

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

Algunas personas creen que debe sobrescribir la memoria utilizada para almacenar la contraseña una vez que ya no la necesita. Esto reduce la ventana de 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 suficiente acceso para secuestrar la memoria JVM para hacer esto. Un atacante con tanto acceso puede atrapar sus eventos clave haciendo que esto sea completamente inútil (AFAIK, así que por favor corríjanme si estoy equivocado).

Actualizar

Gracias a los comentarios tengo que actualizar mi respuesta. Aparentemente hay dos casos en los que esto puede agregar una (muy) pequeña mejora de seguridad ya que reduce el tiempo que una contraseña puede ingresar 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 suponer que lo es y debe ser paranoico con respecto a los volcados de núcleo (puede ser válido si los sistemas no son administrados por un administrador).
  • Su software tiene que ser demasiado paranoico para evitar filtraciones de datos con el acceso del atacante al hardware, usando cosas como TrueCrypt (descontinuado), VeraCrypt o CipherShed .

Si es posible, deshabilitar los volcados del núcleo y el archivo de intercambio se encargarían 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 todavía sería una preocupación válida.

  1. Las cadenas son inmutables en Java si almacena la contraseña como texto sin formato, estará disponible en la memoria hasta que Garbage collector lo borre y dado que las cadenas se usan en el grupo de cadenas para su reutilización, es muy probable que permanezca en la memoria por mucho tiempo. duración, que plantea una amenaza a la seguridad. Dado que cualquiera que tenga acceso al volcado de memoria puede encontrar la contraseña en texto claro
  2. Recomendación de Java utilizando el método getPassword() de JPasswordField que devuelve un método char [] y obsoleto getText() que devuelve la contraseña en texto claro que indica el motivo de seguridad.
  3. toString () siempre existe el riesgo de imprimir texto sin formato en el archivo de registro o en la consola, pero si utiliza Array, no imprimirá el contenido de la matriz, sino que 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 cadena: passwd

    Contraseña del personaje: [C @ 110b2345

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

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

Creo que la motivación es asegurarse de que puede borrar todo rastro de la contraseña en la memoria de inmediato y con certeza después de que se utiliza. Con un char[] puede sobrescribir cada elemento de la matriz con un espacio en blanco o algo seguro. No puede editar el valor interno de una String esa manera.

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

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

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

Y como resultado, 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 documento describe cómo esta circunstancia podría ser potencialmente explotada.

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

Como dice Jon Skeet, no hay otra forma que usar el reflection.

Sin embargo, si la reflexión es una opción para ti, puedes 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 corre

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

Nota: si el carácter de 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 anterior no aparecería en un volcado dynamic, pero si tiene acceso directo a la memoria bruta del proceso, podría verla. En general, debe evitar que cualquiera tenga tal acceso.

Editar: Volviendo a esta respuesta después de un año de investigación de seguridad, me doy cuenta de que tiene la desafortunada consecuencia de que alguna vez compararía las contraseñas de texto sin formato. Por favor no. Use un hash seguro de una sola dirección 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 temporización? Puede ser poco probable, pero en teoría podría medir el tiempo de 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 los ataques de sincronización:

  • Una lección sobre los ataques de tiempo
  • Una discusión sobre el momento de los ataques en la seguridad de la información Stack Exchange
  • Y, por supuesto, la página de Wikipedia Timing Attack

Estas son todas las razones, uno debe elegir la matriz char [] 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 Garbage collector lo borre y como String se utiliza en String pool para su reutilización, hay muchas posibilidades de que permanezca en la memoria por mucho tiempo. duración, que representan una amenaza a la seguridad. Dado que cualquiera que tenga acceso al volcado de memoria puede encontrar la contraseña en texto libre y esa es otra razón por la que siempre debe usar una contraseña encriptada en lugar de texto sin formato. Como las cadenas son inmutables, no hay forma de que se puedan cambiar los contenidos de las cadenas, ya que cualquier cambio producirá una cadena nueva, mientras que si se usa una cadena [], se puede configurar todo en blanco o cero. Por lo tanto, almacenar la contraseña en una matriz de caracteres mitiga claramente el riesgo de robo de contraseña.

2. Java recomienda utilizar el método getPassword () de JPasswordField que devuelve un método char [] y obsoleto getText () que devuelve la contraseña en texto claro que indica el motivo de seguridad. Es bueno seguir los consejos del equipo de Java y seguir el estándar en lugar de ir en contra.

3. Con String siempre existe el riesgo de imprimir texto sin formato en el archivo de registro o en la consola, pero si usa Array, no imprimirá el contenido de la matriz sino que se imprimirá su ubicación de memoria. aunque no es una razón real, pero aún 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 

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

No hay nada que la matriz de caracteres te ofrezca frente a Cadena a menos que la elimines manualmente después de su uso, y no he visto a nadie realmente haciéndolo. Entonces, para mí, la preferencia de char [] frente a String es un poco exagerada.

Eche un vistazo a la biblioteca ampliamente utilizada de Spring Security aquí y pregúntese: si los tipos de Spring Security son incompetentes o las contraseñas [] no tienen mucho sentido. Cuando un hacker desagradable atrapa los vaciados de memoria de tu RAM, asegúrate de que obtendrá todas las contraseñas, incluso si utilizas formas sofisticadas para ocultarlas.

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

Strings are immutable and cannot be altered once they have been created. Creating a password as a string will leave stray references to the password on the heap or on the String pool. Now if someone takes a heap dump of the Java process and carefully scans through he might be able to guess the passwords. Of course these non used strings will be garbage collected but that depends on when the GC kicks in.

On the other side char[] are mutable as soon as the authentication is done you can overwrite them with any character like all M’s or backslashes. Now even if someone takes a heap dump he might not be able to get the passwords which are not currently in use. This gives you more control in the sense like clearing the Object content yourself vs waiting for the GC to do it.

The short and straightforward answer would be because char[] is mutable while String objects are not.

Strings in Java are immutable objects. That is why they can’t be modified once created, and therefore the only way for their contents to be removed from memory is to have them garbage collected. It will be only then when the memory freed by the object can be overwritten, and the data will be gone.

Now garbage collection in Java doesn’t happen at any guaranteed interval. The String can thus persist in memory for a long time, and if a process crashes during this time, the contents of the string may end up in a memory dump or some log.

With a character array , you can read the password, finish working with it as soon as you can, and then immediately change the contents.

1) Since Strings are immutable in Java if you store the password as plain text, it will be available in memory until Garbage collector clears it and since String is used in String pool for reusability, there is pretty high chance that it will remain in memory for the long duration, which poses a security threat. Since anyone who has access to memory dump can find the password in clear text and that’s another reason you should always use an encrypted password than plain text. Since Strings are immutable, there are no way contents of Strings can be changed because any change will produce new String, while if you char[] you can still set all his element as blank or zero. So Storing password in character array mitigates security risk of stealing the password.

2) Java itself recommends using getPassword() method of JPasswordField which returns a char[] and deprecated getText() method which returns password in clear text stating security reason. It’s good to follow advice from Java team and adhering to standard rather than going against it.

String in java is immutable. So whenever a string is created, it will remain in the memory until it is garbage collected. So anyone who has access to the memory can read the value of the string.
If the value of the string is modified then it will end up creating a new string. So both the original value and the modified value stay in the memory until it is garbage collected.

With the character array, the contents of the array can be modified or erased once the purpose of the password is served. The original contents of the array will not be found in memory after it is modified and even before the garbage collection kicks in.

Because of the security concern it is better to store password as a character array.

String is immutable and it goes to the string pool. Once written, it cannot be overwritten.

char[] is an array which you should overwrite once you used the password and this is how it should be done:

 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; } } 

One scenario where the attacker could use it is a crashdump – when the JVM crashes and generates a memory dump – you will be able to see the password.

That is not necessarily a malicious external attacker. This could be a support user that has access to the server for monitoring purposes. He could peek into a crashdump and find the passwords.