que - ¿Cómo puedo garantizar la destrucción de un objeto String en Java?
que es string en java (9)
Entonces, aquí están las malas noticias. Me sorprende que nadie lo haya mencionado todavía. con los recolectores de basura modernos, incluso todo el concepto de char [] se rompe. Independientemente de si usa un String o un char [], los datos pueden terminar viviendo en la memoria por quién sabe cuánto. ¿porqué es eso? porque los jvms modernos usan recolectores de basura generacionales que, en resumen, copian objetos por todos lados . así que, incluso si usa un char [], la memoria real que utiliza podría copiarse a varias ubicaciones en el montón, dejando copias de la contraseña donde quiera que vaya (y ninguna función gc va a poner a cero la memoria anterior). entonces, cuando pone a cero la instancia que tiene al final, solo está poniendo a cero la última versión en la memoria.
larga historia, en resumen, no hay una forma a prueba de balas para manejarlo. tienes que confiar en la persona.
Un miembro de mi empresa necesita modificar los datos de una base de datos de SQL Server a través de un programa que hice. El programa usó la autenticación de Windows al principio, y solicité a los DBA que le dieran a este usuario específico acceso de escritura a dicha base de datos.
No estaban dispuestos a hacer esto, y en su lugar dieron acceso de escritura a mi cuenta de usuario de Windows.
Como confío en el tipo pero no lo suficiente para dejarlo trabajar 90 minutos con mi sesión abierta, solo agregaré un mensaje de inicio de sesión a mi programa, solicitando una combinación de nombre de usuario y contraseña, e iniciar sesión en SQL Server con él. Voy a iniciar sesión y confío en mi aplicación para que pueda hacer solo lo que necesita.
Esto, sin embargo, plantea un pequeño riesgo de seguridad. El tutorial de campos de contraseña en el sitio de Sun Oracle establece que las contraseñas deben mantenerse en la cantidad mínima de tiempo requerida en la memoria, y para este fin, el método getPassword
devuelve una matriz char[]
que puede poner a cero una vez que haya terminado con ella.
Sin embargo, la clase DriverManager
de Java solo acepta objetos String
como contraseñas, por lo que no podré deshacerme de la contraseña tan pronto como haya terminado con ella. Y dado que mi aplicación es relativamente baja en asignaciones y requisitos de memoria, ¿quién sabe cuánto tiempo sobrevivirá en la memoria? El programa se ejecutará por un tiempo bastante largo, como se indicó anteriormente.
Por supuesto, no puedo controlar lo que hagan las clases JDBC de SQL Server con mi contraseña, pero esperaba poder controlar lo que hago con mi contraseña.
¿Existe una forma segura de destruir / poner a cero un objeto String
con Java? Sé que ambos están en contra del lenguaje (la destrucción de objetos no es determinista, y los objetos String
son inmutables), y System.gc()
es algo impredecible, pero aún así; ¿alguna idea?
Interesante pregunta. Algunos Googeling revelaron esto: http://securesoftware.blogspot.com/2009/01/java-security-why-not-to-use-string.html . De acuerdo con el comentario, no hará la diferencia.
¿Qué sucede si no almacena el String en una variable pero lo pasa a través de un nuevo String (char [])?
No estoy seguro de la clase DriverManager
.
En general, tiene razón, la recomendación es almacenar la contraseña en matrices de caracteres y borrar explícitamente la memoria después del uso.
El ejemplo más común:
KeyStore store = KeyStore. getInstance(KeyStore, getDefaultType()) ;
char[] password = new char[] {''s'',''e'',''c'',''r'',''e'',''t''};
store .load(is, password );
//After finished clear password!!!
Arrays. fill(password, ''/u0000'' ) ;
En JSSE y JCA el diseño tenía exactamente esto en mente. Es por eso que las API esperan un char[]
y no un String.
Como, como bien sabe, las cadenas son inmutables, la contraseña es elegible para la futura recolección de basura y no puede restablecerla después. Esto puede causar riesgos de seguridad por programas maliciosos que fisgonean áreas de memoria.
No creo que en este caso lo que está buscando sea un trabajo alternativo.
En realidad, hay una pregunta similar aquí:
¿Por qué Driver Manager no usa matrices de caracteres?
pero no hay una respuesta clara.
Parece que el concepto es que la contraseña ya está almacenada en un archivo de propiedades (ya hay un constructor de DriverManager que acepta propiedades) y por lo tanto el archivo ya impone un riesgo mayor que la carga real de la contraseña de un archivo a una cadena.
O los diseñadores de la API tenían algunas suposiciones sobre la seguridad de la máquina que accede al DB.
Creo que la opción más segura sería tratar de ver, si es posible, cómo el DriverManager usa la contraseña, es decir, se aferra a una referencia interna, etc.
No hay una manera regular de forzar la recolección de basura. Es posible a través de una llamada nativa, pero no sé si funcionaría para Strings, ya que se agrupan.
Tal vez un enfoque alternativo ayudará? No estoy tan familiarizado con SQL Server, pero en Oracle, la práctica es que el usuario A tenga los objetos de la base de datos y un conjunto de procedimientos almacenados, y que el usuario B no tenga nada más que permiso de ejecución en los procedimientos. De esta forma, la contraseña ya no es un problema.
En su caso, será su usuario el propietario de todo el objeto de la base de datos y los procedimientos almacenados, y su empleado necesitará privilegios de ejecución para los procesos almacenados, lo que los DBA serán menos reacios a ofrecer.
Puede cambiar el valor del char[]
interno char[]
utilizando reflexión.
Debe tener cuidado de cambiarlo con una matriz de la misma longitud o actualizar también el campo de count
. Si desea poder utilizarlo como una entrada en un conjunto o como un valor en el mapa, tendrá que volver a calcular el código hash y establecer el valor del campo hashCode
.
Dicho esto, el código mínimo para lograr esto es
String password = "password";
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] chars = (char[]) valueField.get(password);
chars[0] = Character.valueOf(''h'');
System.out.println(password);
Si el administrador del controlador JDBC no retiene la cadena (un gran si), no me preocuparía forzar su destrucción. Una JVM moderna, todavía ejecuta la recolección de basura con bastante prontitud incluso con mucha memoria disponible. El problema es si la recolección de basura es efectiva "borrado seguro". Dudo que lo sea. Supongo que simplemente olvida la referencia a esa ubicación de memoria y no pone a cero nada.
Solo puedo pensar en una solución usando la reflexión. Puede usar reflection para invocar el constructor privado que usa una matriz de caracteres compartida:
char[] chars = {''a'', ''b'', ''c''};
Constructor<String> con = String.class.getDeclaredConstructor(int.class, int.class, char[].class);
con.setAccessible(true);
String password = con.newInstance(0, chars.length, chars);
System.out.println(password);
//erase it
Arrays.fill(chars, ''/0'');
System.out.println(password);
Editar
Para cualquiera que piense que esta es una precaución a prueba de fallas o incluso útil, le animo a leer la respuesta de jtahlborn al menos para una advertencia.
si es absolutamente necesario, mantenga una WeakReference en la cadena y continúe engullendo la memoria hasta que fuerce la recolección de basura de la cadena que puede detectar probando si la referencia débil se ha vuelto nula. esto aún puede dejar los bytes en el espacio de direcciones del proceso. ¿pueden ser un par de batidores más del recolector de basura que te darían comodidad? por lo tanto, después de anular la referencia de debilidad de cadena original, cree otra referencia débil y agite hasta que se ponga a cero, lo que implicaría que se realizó un ciclo completo de recolección de basura.
de alguna manera, tengo que agregar LOL a esto a pesar de que mi respuesta anterior es completamente seria :)
así que no podré deshacerme de la contraseña tan pronto como haya terminado con ella.
Por qué no?
Si estás recibiendo una conexión a través de
Driver.getConnection
solo tienes que pasar la contraseña y dejar que se gc''ed.
void loginScreen() {
...
connect( new String( passwordField.getPassword() ) );
...
}
...
void connect( String withPassword ) {
connection = DriverManager.getConnection( url, user, withPassword );
...
}//<-- in this line there won''t be any reference to your password anymore.
Cuando el control vuelve del método de connect
, ya no hay una referencia para su contraseña. Nadie puede usarlo más. Si necesita crear una nueva sesión, debe invocar nuevamente "connect" con una nueva contraseña. La referencia al objeto que contiene su información se pierde.