¿Por qué se prefiere char [] sobre String para las contraseñas?
Frecuentes
Visto 453,134 equipos
3667
En Swing, el campo de contraseña tiene un getPassword()
(devoluciones char[]
) método en lugar del habitual getText()
(devoluciones String
) método. Del mismo modo, me he encontrado con una sugerencia de no utilizar String
para manejar contraseñas.
Por que String
¿Representan una amenaza para la seguridad cuando se trata de contraseñas? Se siente incómodo de usar char[]
.
18 Respuestas
4572
Las cuerdas son inmutables. Eso significa que una vez que haya creado el String
, si otro proceso puede volcar memoria, no hay forma (aparte de reflexión) puede deshacerse de los datos antes recolección de basura patadas en.
Con una matriz, puede borrar explícitamente los datos una vez que haya terminado. Puede sobrescribir la matriz con lo que quiera y la contraseña no estará presente en ninguna parte del sistema, incluso antes de la recolección de basura.
Entonces si, esto is un problema de seguridad, pero incluso usando 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 está moviendo el recolector de basura dejen copias perdidas de los datos en la memoria. Creo que esto es específico de la implementación: el recolector de basura puede borre toda la memoria a medida que avanza, para evitar este tipo de cosas. Incluso si lo hace, todavía existe el tiempo durante el cual el char[]
contiene los personajes reales como una ventana de ataque.
Respondido 20 Abr '18, 18:04
1291
Si bien otras sugerencias aquí parecen válidas, hay otra buena razón. Con llano String
tienes muchas más posibilidades de imprimir accidentalmente la contraseña en los 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: [C@5829428e
Respondido el 19 de enero de 12 a las 18:01
@voo, pero dudo que inicie sesión mediante escritura directa en transmisión y concatenación. marco de registro transformaría el char [] en un buen resultado - mejorsss
@ Thr4wn Implementación predeterminada de toString
is classname@hashcode
. [C
representa char[]
, el resto es código hash hexadecimal. - Konrad Garús
Idea interesante. Me gustaría señalar que esto no se transpone a Scala, que tiene un toString significativo para matrices. - mauhiz
Escribiría un Password
tipo de clase para esto. Es menos oscuro y más difícil pasar accidentalmente a algún lado. - usuario1804599
¿Por qué alguien asumiría que la matriz de caracteres se convertiría en un Objeto? No estoy seguro de entender por qué a todos les gusta esta respuesta. Suponga que hizo esto: System.out.println ("Password" .toCharArray ()); - GC_
723
Para citar un documento oficial, el Guía de arquitectura de criptografía de Java dice esto sobre char[]
vs String
contraseñas (sobre el cifrado basado en contraseñas, pero esto se trata más generalmente de contraseñas, por supuesto):
Parecería lógico recopilar y almacenar la contraseña en un objeto de tipo
java.lang.String
. Sin embargo, aquí está la advertencia:Object
s de tipoString
son inmutables, es decir, no hay métodos definidos que le permitan cambiar (sobrescribir) o poner a cero el contenido de unString
después de su uso. Esta característica haceString
Objetos inadecuados para almacenar información confidencial de seguridad, como contraseñas de usuario. Siempre debe recopilar y almacenar información confidencial de seguridad en unchar
matriz en su lugar.
Directriz 2-2 de las Directrices de codificación segura para el lenguaje de programación Java, versión 4.0 también dice algo similar (aunque originalmente está en el contexto del registro):
Directriz 2-2: No registre información altamente confidencial
Parte de la información, como los números de seguro social (SSN) y las contraseñas, es muy confidencial. Esta información no debe conservarse más tiempo del necesario ni donde pueda ser vista, ni siquiera por los administradores. Por ejemplo, no debe enviarse a archivos de registro y su presencia no debe ser detectable mediante búsquedas. Es posible que algunos datos transitorios se mantengan en estructuras de datos mutables, como matrices de caracteres, y se borren inmediatamente después de su uso. El borrado de estructuras de datos ha reducido la eficacia en los sistemas de ejecución típicos de Java, ya que los objetos se mueven en la memoria de forma transparente para el 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 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.
Respondido el 17 de enero de 12 a las 03:01
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. - mejorsss
@bestass, ¿puede citar también una referencia? - user961954
@bestass lo siento, pero String
se entiende bastante bien y cómo se comporta en la JVM ... hay buenas razones para usar char[]
en lugar de String
cuando se trata de contraseñas de forma segura. - SnakeDoc
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 se debe actuar y desechar, nunca almacenar en la memoria? - Dawesi
@Dawesi- At which point
- eso es específico de la aplicación, 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 tipo). 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 obtenga, póngalo en un carácter [], haga lo que tenga que hacer con él, luego configure todo en '0' y deje que el gc reclamarlo. - luis.espinal
376
Matrices de caracteres (char[]
) se puede borrar después de su uso estableciendo 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 char[]
se utiliza, después de purgar los datos con ceros, la contraseña es segura.
respondido 10 mar '12, 17:03
No seguro por defecto. Si hablamos de una aplicación web, la mayoría de los contenedores web pasarán la contraseña al HttpServletRequest
objeto en texto plano. 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 recopile. (Siempre que sea.) - avgvstvs
@avgvstvs: las cadenas no se mueven automáticamente al espacio permgen, eso solo se aplica a las cadenas internas. Además de eso, el espacio permgen también está sujeto a recolección de basura, solo que a un ritmo menor. El problema real con el espacio permgen es su tamaño fijo, que es exactamente la razón por la que nadie debería llamar sin pensar intern()
en cadenas arbitrarias. Pero tienes razón en que el String
instancias existen en primer lugar (hasta que se recopilan) y convertirlas en char[]
matrices posteriores no lo cambia. - Holger
@Holger ver docs.oracle.com/javase/specs/jvms/se6/html/… "De lo contrario, se crea una nueva instancia de la clase String que contiene la secuencia de caracteres Unicode dada por la estructura CONSTANT_String_info; esa instancia de clase es el resultado de la derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String". En 1.6, la JVM llamaba a un interno cuando detectaba secuencias idénticas. - avgvstvs
@Holger, tienes razón, combiné el grupo constante y el grupo de cadenas, pero también es falso que el espacio permgen , solamente aplicado a cuerdas internados. 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 dijiste, new String()
or StringBuilder.toString()
Administré aplicaciones con muchas constantes de cadena y, como resultado, tuvimos muchos permgen creep. Hasta el 1.7. - avgvstvs
@avgvstvs: bueno, las constantes de cadena son, como manda el JLS, siempre internas, de ahí la declaración de que las cadenas internas terminaron en el espacio permgen, aplicado implícitamente a las constantes de cadena. La única diferencia es que las constantes de cadena se crearon en el espacio permgen en primer lugar, mientras que las llamadas intern()
en 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 del mismo contenido compartiendo ese objeto ... - Holger
234
Algunas personas creen que debe sobrescribir la memoria utilizada para almacenar la contraseña una vez que ya no la necesite. Esto reduce la ventana de tiempo que un atacante tiene para 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 capturar sus eventos clave haciendo que esto sea completamente inútil (AFAIK, así que corríjame si me equivoco).
Actualización
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 está y debe ser paranoico acerca de los volcados de memoria (puede ser válido si los sistemas no son administrados por un administrador).
- Su software tiene que ser demasiado paranoico para evitar fugas de datos con el atacante obteniendo acceso al hardware, usando cosas como TrueCrypt (interrumpido), VeraCrypto Cobertizo de cifrado.
Si es posible, la desactivación de los volcados de memoria y el archivo de intercambio solucionaría 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 aún sería una preocupación válida.
Respondido 19 Jul 15, 23:07
Reemplazaría "completamente inútil" con "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 una falla en su aplicación. En ese caso, no podrá instalar un keylogger, pero podría analizar el volcado del núcleo. - Joaquín Sauer
Limpiar los datos no cifrados de la memoria tan pronto como haya terminado se considera una mejor práctica, no porque sea infalible (no lo es); sino porque reduce su nivel de exposición a amenazas. Los ataques en tiempo real no se evitan al hacer esto; pero debido a que sirve como una herramienta de mitigación de daños al reducir significativamente la cantidad de datos que se exponen en un ataque retroactivo en una instantánea de la memoria (por ejemplo, una copia de la memoria de sus aplicaciones que se escribió en un archivo de intercambio, o que se extrajo de la memoria extraída) desde un servidor en ejecución y se movió a uno diferente antes de que fallara su estado). - Dan está jugando a la luz del fuego
Tiendo a estar de acuerdo con la actitud de esta respuesta. Me atrevería a proponer que la mayoría de las violaciones de seguridad de importancia ocurren en un nivel de abstracción mucho más alto que los bits en la memoria. Claro, probablemente hay escenarios en los sistemas de defensa hiperseguros donde esto puede ser una preocupación considerable, pero pensar seriamente en este nivel es excesivo para el 99% de las aplicaciones donde se aprovechan .NET o Java (en lo que respecta a la recolección de basura). - reydango
Después de la penetración de Heartbleed en la memoria del servidor, revelando contraseñas, reemplazaría la cadena "solo una pequeña mejora de seguridad" por "absolutamente esencial para no usar String para las contraseñas, sino usar un char [] en su lugar". - pedro vdl
@PetervdL heartbleed solo permitía la lectura de una colección específica reutilizada de búferes (usados tanto para datos críticos de seguridad como para E / S de red sin borrar entre ellos, por razones de rendimiento), no puede combinar esto con una cadena Java ya que, por diseño, no son reutilizables . Tampoco puede leer en la memoria aleatoria usando Java para obtener el contenido de una cadena. Los problemas de lenguaje y diseño que conducen a heartbleed simplemente no son posibles con Java Strings. - josefx
92
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 rastro de la contraseña en la memoria de inmediato y con certeza después de que se use. 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 un String
de esa manera.
Pero eso por sí solo no es una buena respuesta; ¿Por qué no asegurarse de que una referencia a la char[]
or String
no se escapa? Entonces no hay problema de seguridad. Pero la cosa es que String
los objetos pueden ser intern()
ed en teoría y mantenido vivo dentro de la piscina constante. Supongo que usando char[]
prohíbe esta posibilidad.
Respondido el 16 de enero de 12 a las 14:01
Yo no diría que el problema es que sus referencias "escaparán" o no. Es solo que las cadenas permanecerán sin modificar en la memoria durante un tiempo adicional, mientras que char[]
se puede modificar, y luego es irrelevante si se recopila o no. Y dado que la internación de cadenas debe realizarse explícitamente para no literales, es lo mismo que decir que un char[]
puede ser referenciado por un campo estático. - Groo
¿No está la contraseña en la memoria como una cadena de la publicación del formulario? - Dawesi
76
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 de reemplazar las cadenas de contraseña con char[]
en todas partes (lo que por supuesto es algo 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 el PrivateKey clase. 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 bien, en este caso, olfatear la contraseña por sí solo no le ayudaría mucho siempre que el acceso físico al archivo de claves 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 de forma múltiple, 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 un 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 esto describe cómo podría explotarse potencialmente esta circunstancia.
La biblioteca OpenSSL, por ejemplo, sobrescribe las secciones críticas de la memoria antes de que se liberen las claves privadas. Dado que Java se recolecta como basura, necesitaríamos métodos explícitos para borrar e invalidar la información privada de las claves de Java, que deben aplicarse inmediatamente después de usar la clave.
Respondido el 19 de enero de 12 a las 19:01
Una forma de evitar esto es utilizar una implementación de PrivateKey
que en realidad no carga 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 usando algo como la tienda NSS (que comparte la mayor parte de su implementación con la PKCS11
tipo de tienda en Java) es mejor. los KeychainStore
(Almacén de claves OSX) carga el contenido completo de la clave privada en su PrivateKey
instancias, pero no debería ser necesario. (No estoy seguro de lo que WINDOWS-MY
KeyStore lo hace en Windows.) - Marrón
@Bruno Claro, los tokens basados en hardware no sufren de esto, pero ¿qué pasa con las situaciones en las que se ve 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. - realzar
Absolutamente, solo 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 en realidad nunca ve la clave privada, la biblioteca SSL subyacente proporcionada por el sistema operativo habla 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 limpiaría mejor la memoria. - Marrón
@Bruno: Interesante idea, una capa de indirección adicional que se encarga de borrar la memoria resolvería esto de manera transparente. ¿Escribir un contenedor PKCS # 11 para el almacén de claves de software ya podría funcionar? - realzar
Es curioso que digas que tienen mucho cuidado al usar char[]
"ahora", porque estoy viendo este nuevo y agradable ConnectionBuilder
clase agregada en JDK 9, y todavía tiene password(String)
y no hay opción de pasar char[]
en absoluto. Parece que con Java hay mucho "haz lo que digo, no lo que hago". - Hakanai
58
Como afirma Jon Skeet, no hay otra forma que utilizar la reflexión.
Sin embargo, si la reflexión es una opción para ti, puedes 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 pila, pero si tiene acceso directo a la memoria sin procesar del proceso, podría verla. En general, debe evitar que cualquier persona tenga dicho acceso.
Respondido 08 Jul 15, 10:07
Es mejor hacer también algo para evitar imprimir la longitud de la contraseña que obtenemos de '***********'
. - chux - Reincorporar a Monica
@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 Unsafe. ;) - pedro laurey
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 contraseña String. Improbable, pero posible ... - mermelada
@PeterLawrey Debe estar habilitado con un argumento JVM, pero está ahí. Puede leer sobre esto aquí: blog.codecentric.de/en/2014/08/… - mermelada
Existe una alta probabilidad de que la contraseña aún esté en Scanner
el búfer interno y, dado que no usó System.console().readPassword()
, de forma legible en la ventana de la consola. Pero para la mayoría de los casos de uso práctico, la duración de usePassword
La ejecución es el problema real. Por ejemplo, cuando se realiza una conexión con otra máquina, se tarda mucho tiempo 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
51
Estas son todas las razones, uno debe elegir un carbonizarse[] matriz en lugar de Cordón para una 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 String se usa en el grupo de cadenas para su reutilización, existe una posibilidad bastante alta de que permanezca en 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 se pueda cambiar el contenido de las cadenas 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. El propio Java recomienda usar el método getPassword () de JPasswordField que devuelve un char [], en lugar del método obsoleto getText () que devuelve contraseñas en texto sin cifrar 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 de este blog. Espero que esto ayude.
respondido 19 nov., 18:14
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. - Suerte
¿Cuál es la diferencia entre 1.) System.out.println ("Contraseña de carácter:" + charPassword); y 2.) System.out.println (charPassword); Porque da el mismo "desconocido" como salida. - vaibhav_sharma
@Lucky Desafortunadamente, la respuesta anterior a la que vinculó fue plagiada del mismo blog que esta respuesta, y ahora se ha eliminado. Ver meta.stackoverflow.com/questions/389144/… . Esta respuesta simplemente se cortó y pegó de ese mismo blog y no agregó nada, por lo que debería haber sido simplemente un comentario que enlaza con la fuente original. - skomisa
45
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ías contraseñas de texto sin formato. Por favor no lo hagas. Utilice un hash unidireccional seguro con sal y un número razonable de iteraciones. Considere usar una biblioteca: ¡esto es difícil de hacer bien!
Respuesta original: ¿Qué pasa con el hecho de que String.equals () usa evaluación de cortocircuitoy, por tanto, es vulnerable a un ataque de tiempo? Puede ser poco probable, pero podrías teóricamente cronometra la comparación de contraseñas 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:
- Una lección sobre la sincronización de los ataques
- Una discusión sobre los ataques de tiempo sobre el intercambio de pilas de seguridad de la información
- Y por supuesto, el Página de Wikipedia de Timing Attack
contestado el 23 de mayo de 17 a las 13:05
Pero eso también podría estar ahí en la comparación char [], en algún lugar también estaríamos haciendo lo mismo en la validación de contraseñas. entonces, ¿cómo es char [] mejor que string? - mohit kanwar
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 existe un método explícito de comparación de contraseñas en Java para contraseñas basadas en cadenas o char []. Yo diría que la tentación de usar compare () para Strings es una buena razón para ir con char []. De esa manera, al menos tiene control sobre cómo se realiza la comparación (sin extender String, lo cual es una molestia). - Teoría de grafos
Además de comparar contraseñas de texto sin formato no es lo correcto de todos modos, la tentación de usar Arrays.equals
en 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 clave dedicadas, esta sesión de preguntas y respuestas es solo sobre un hábito fuera de ellos, digamos, p. ej. JPasswordField
, usar char[]
en lugar de String
(donde los algoritmos reales usan byte[]
de todas formas). - 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
44
No hay nada que char array le brinde frente a String a menos que lo limpie manualmente después de su uso, y no he visto a nadie que realmente lo haga. Entonces, para mí, la preferencia de char [] frente a String es un poco exagerada.
Echa un vistazo a la ampliamente utilizado Biblioteca de Spring Security aquí y pregúntese: ¿los chicos de Spring Security son incompetentes o las contraseñas char [] no tienen mucho sentido? Cuando algún pirata informático desagradable se apodere de los volcados de memoria de su RAM, asegúrese de que obtendrá todas las contraseñas, incluso si utiliza formas sofisticadas de ocultarlas.
Sin embargo, Java cambia todo el tiempo y algunas características aterradoras como Función de deduplicación de cadenas de Java 8 podría internar objetos String sin su conocimiento. Pero esa es una conversación diferente.
Respondido el 17 de enero de 20 a las 19:01
¿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 al permitir 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 matriz de ese contenido que estará vivo al menos mientras la Cadena más longeva de ese contenido 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 característica maravillosa los almacenará a ambos en un solo carácter [] haciendo evidente que son iguales, no estoy seguro si eso es un gran riesgo pero aún así ... Oleg Mijeev
Si tiene acceso a la memoria del montón y a ambas instancias de cadenas, no importa si las cadenas apuntan a la misma matriz oa dos matrices del mismo contenido, cada una es fácil de averiguar. Especialmente, ya que de todos modos es irrelevante. Si está en este punto, tome 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, tiene que estar en texto sin cifrar en la memoria durante algún tiempo, 10 ms, incluso si es solo para crear un hash salado a partir de ella. 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 interna cadenas, se guardan en la memoria durante mucho más tiempo. El sistema que no se reinicia durante meses recopilará muchos de estos. Solo teorizando. - Oleg Mijeev
Parece que tiene un malentendido fundamental sobre la deduplicación de cadenas. No "intercala cadenas", todo lo que hace es permitir que las cadenas con el mismo contenido apunten a la misma matriz, 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 excepto una pueden ser reclamadas y sobrescritas por otros objetos inmediatamente. Estas cadenas todavía se recopilan como cualquier otra cadena. Tal vez sea útil, si comprende que la deduplicación la realiza realmente el recolector de basura, solo para cadenas que han sobrevivido a múltiples ciclos de GC. - Holger
33
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 perdidas a la contraseña en el montón o en el grupo de cadenas. Ahora bien, si alguien realiza un volcado del proceso de Java y lo escanea cuidadosamente, podría adivinar las contraseñas. Por supuesto, estas cadenas no utilizadas se recolectarán como basura, pero eso depende de cuándo se active el GC.
Por otro lado, los caracteres [] son mutables tan pronto como se realiza la autenticación, puede sobrescribirlos con cualquier carácter, como todas las M o barras diagonales inversas. Ahora, incluso si alguien realiza 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 de borrar el contenido del Objeto usted mismo en lugar de esperar a que el GC lo haga.
Respondido 28 Jul 15, 11:07
Solo serán GC si la JVM en cuestión es> 1.6. Antes de 1.7, todas las cadenas se almacenaron en permgen. - avgvstvs
@avgvstvs: "todas las cadenas se almacenaron en permgen" es un error. Solo las cadenas internas se almacenaban allí y, si no se originaban a partir de literales de cadena referenciados por código, seguían siendo basura recolectada. Solo piensa en ello. Si las cadenas generalmente nunca se sometieron a GC en las JVM antes de 1.7, ¿cómo podría una aplicación Java sobrevivir más de unos pocos minutos? - Holger
@Holger Esto es falso. Internado strings
Y el String
grupo (grupo de cadenas utilizadas anteriormente) se almacenaron AMBOS 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 marcada Strings
para ver si eran el mismo valor de referencia, y llamarían String.intern()
PARA TI. El resultado fue que cada vez que la JVM detectaba cadenas idénticas en el constant_pool
o apilarlos los movería a permgen. Y trabajé en varias aplicaciones con "creeping permgen" hasta la 1.7. Fue un verdadero problema. - avgvstvs
Entonces, para recapitular: hasta 1.7, las cadenas comenzaban en el montón, cuando se usaban se colocaban en el constant_pool
que estaba ubicado EN permgen, y luego, si una cadena se usaba 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 grupo de cadenas de tiempo de ejecución que contiene literales de cadena y una cadena interna explícitamente, pero no otra. Y cada clase tiene su grupo constante que contiene en tiempo de compilación constantes. Estas cadenas se agregan automáticamente al grupo de tiempo de ejecución, pero solo estos, no todas las cuerdas. - Holger
28
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 falla y genera un volcado de memoria, podrá ver la contraseña.
Eso no es necesariamente un atacante externo malintencionado. Este podría ser un usuario de soporte que tiene acceso al servidor con fines de supervisión. Podría echar un vistazo a un vertedero y encontrar las contraseñas.
Respondido el 08 de enero de 20 a las 15:01
ch = nulo; no puedes hacer esto - Yugerten
Pero no request.getPassword()
¿Ya creaste la cadena y la agregaste al grupo? - Tvde1
ch = '0'
cambia la variable local ch
; no tiene ningún efecto en la matriz. Y su ejemplo no tiene sentido de todos modos, comienza con una instancia de cadena que llama toCharArray()
encendido, creando una nueva matriz e incluso cuando sobrescribe la nueva matriz correctamente, no cambia la instancia de la cadena, por lo que no tiene ninguna ventaja sobre el uso de la instancia de la cadena. - Holger
@Holger gracias. Se corrigió el código de limpieza de la matriz de caracteres. - LCA
Simplemente puede usar Arrays.fill(pass, '0');
- Holger
24
La respuesta corta y sencilla sería porque char[]
es mutable mientras String
los objetos no lo son.
Strings
en Java son objetos inmutables. Es por eso que no se pueden modificar una vez creados y, por lo tanto, la única forma de eliminar su contenido de la memoria es recolectar la basura. Será solo entonces cuando la memoria liberada por el objeto se pueda sobrescribir y los datos desaparezcan.
Ahora la recolección de basura en Java no ocurre en ningún intervalo garantizado. los String
Por lo tanto, puede persistir en la memoria durante mucho tiempo, y si un proceso falla durante este tiempo, el contenido de la cadena puede terminar en un volcado de memoria o en algún registro.
Con una conjunto de caracteres, puede leer la contraseña, terminar de trabajar con ella lo antes posible y luego cambiar inmediatamente el contenido.
Respondido el 05 de enero de 18 a las 23:01
@fallenidol Para nada. Lea atentamente y encontrará las diferencias. - pritam banerjee
13
La cadena en java es inmutable. Entonces, cada vez que se crea una cadena, permanecerá en la memoria hasta que se recolecte la basura. Entonces, 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. Entonces, tanto el valor original como el valor modificado permanecen en la memoria hasta que se recolectan como 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 por la seguridad, es mejor almacenar la contraseña como una matriz de caracteres.
Respondido 28 Jul 17, 07:07
10
Cadena del caso:
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
MATRIZ DE CARACTERES DEL CASO:
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
Respondido 24 Abr '20, 13:04
3
Es discutible si debería usar String o Char [] para este propósito porque ambos tienen sus ventajas y desventajas. Depende de lo que necesite el usuario.
Dado que las cadenas en Java son inmutables, cada vez que alguien intenta 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 de alguna manera obtuvo la ubicación de 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 valores. Entonces, cuando termina de usarse, se limpia y nadie podría saber sobre la información que había almacenado.
Con base en las circunstancias anteriores, uno puede tener una idea de si optar por String o por Char [] para sus requisitos.
contestado el 09 de mayo de 19 a las 12:05
1
Muchas buenas respuestas arriba. Hay otro punto que estoy asumiendo (corríjame si me equivoco). De forma predeterminada, Java usa UTF-16 para almacenar cadenas. El uso de matrices de caracteres char [] array facilita la utilización de caracteres regionales, Unicode, etc. Esta técnica permite que todo el conjunto de caracteres se respete por igual para almacenar las contraseñas y, en adelante, no iniciará ciertos problemas de cifrado debido a la confusión del conjunto de caracteres. Finalmente, usando la matriz de caracteres, podemos convertir la matriz de contraseñas en nuestra cadena de caracteres deseada.
respondido 08 mar '21, 13:03
Si un proceso tiene acceso a la memoria de su aplicación, entonces eso ya es una brecha de seguridad, ¿verdad? - Yeti
@Yeti: Sí, pero no es como si fuera blanco y negro. Si solo pueden obtener una instantánea de la memoria, entonces querrá reducir el daño que puede causar esa instantánea, o reducir la ventana durante la cual se puede tomar una instantánea realmente seria. - jon skeet
Un método de ataque común es ejecutar un proceso que asigna gran cantidad de memoria y luego la escanea 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 depende de que otros procesos mueran sin borrar primero los datos confidenciales y el sistema operativo tampoco borra la memoria (o los búferes de página) antes de ponerlos a disposición de un nuevo proceso. Borrar contraseñas almacenadas en
char[]
ubicaciones cortan esa línea de ataque, algo que no es posible cuando se usaString
. - ted saltoSi el sistema operativo no borra la memoria antes de pasarla a otro proceso, ¡el sistema operativo tiene problemas de seguridad importantes! Sin embargo, técnicamente, la limpieza se realiza a menudo 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 antigua. - Mikko Rantalainen
@PrateekPande: Solo estaría en el grupo literal si estuviera presente en el código fuente, o internado explícitamente. Ambas son malas ideas en general ... - jon skeet