password encrypt decrypt data cipher and java android security cryptography secret-key

java - data - encrypt and decrypt password in android



¿Cómo cero una clave secreta en Java? (8)

En otras palabras, ¿el método getEncoded devuelve una copia o referencia a la clave real?

key.getEncoded() devolverá una referencia a una matriz.

Si el contenido de la clave se descarta cuando lo hace, Array.fill depende de si la matriz retornada respalda o no la clave. Dada la documentación, me parece que la codificación de la clave es otra representación de la clave, es decir, que la clave no está respaldada por la matriz devuelta.

Sin embargo, es fácil de descubrir. Pruebe lo siguiente:

byte[] rawKey = key.getEncoded(); Arrays.fill(rawKey, (byte) 0); byte[] again = key.getEncoded(); Log.d(Arrays.equals(rawKey, again));

Si el resultado es false , usted sabe que la clave aún está almacenada en SecretKey .

¿Es suficiente el siguiente código Java para borrar la clave secreta en la memoria (configurando todo su valor de byte en 0)?

zerorize(SecretKey key) { byte[] rawKey = key.getEncoded(); Arrays.fill(rawKey, (byte) 0); }

En otras palabras, ¿el método getEncoded devuelve una copia o referencia a la clave real? Si se devuelve una copia, ¿cómo puedo borrar la clave secreta como medida de seguridad?


Antes de intentar borrar la clave, primero debe verificar si la implementación de la interfaz de SecretKey también implementa la interfaz javax.security.auth.Destroyable . Si es así, prefiero eso por supuesto.


Dependiendo de la tecnología que alimenta el recolector de basura, cualquier objeto individual puede moverse (es decir, copiarse) en la memoria física en cualquier momento, por lo que no puede estar seguro de que realmente destruirá la clave poniendo a cero una matriz, suponiendo que puede acceder " la "matriz que contiene la clave, y no una copia de la misma".

En palabras más cortas: si su modelo de seguridad y contexto requieren claves de puesta a cero, entonces no debería usar Java (o casi nada más que C y ensamblaje).


Estoy bastante seguro de que borrar rawKey no afectará los datos en la key .

No creo que haya una manera en general de borrar los datos en un SecretKey. Las clases de implementación específicas pueden proporcionar eso, pero no conozco ninguna que lo haga. En Android, el riesgo de dejar los datos sin resolver es muy bajo. Cada aplicación se ejecuta en su propio proceso y su memoria no es visible desde el exterior.

Supongo que hay un escenario de ataque en el que un proceso con privilegios de root puede tomar instantáneas de la memoria y enviarlas a alguna supercomputadora en algún lugar para su análisis, con la esperanza de descubrir las claves secretas de alguien. Pero nunca había oído hablar de tal ataque, y me parece que no es competitivo con otras formas de acceder a un sistema. ¿Hay alguna razón por la que te preocupe esta particular vulnerabilidad hipotética?


Excepto los valores primitivos, todo lo demás en Java siempre pasa por referencia, incluidas las matrices, por lo que sí, está borrando la matriz de bytes dada correctamente.

Sin embargo, es probable que la clase SecretKey todavía contenga los datos necesarios para generar esa matriz de bytes, incluyendo eventualmente otra copia de la matriz de bytes dada, por lo que debe investigar cómo borrar esos datos también.


GetEncoded devuelve una copia de la clave secreta (por lo que el borrado no tiene ningún efecto sobre los datos de la clave secreta) y destruye por defecto arroja DestroyFailedException, que es peor que inútil. También solo está disponible en 1.8+, por lo que Android no tiene suerte. Aquí hay un truco que usa la introspección para (1) invocar destruir si está disponible y no arroja una excepción, de lo contrario (2) ponga a cero los datos clave y establezca la referencia como nula.

package kiss.cipher; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import javax.crypto.spec.SecretKeySpec; /** * Created by wmacevoy on 10/12/16. */ public class CloseableKey implements AutoCloseable { // forward portable to JDK 1.8 to destroy keys // but usable in older JDK''s static final Method DESTROY; static final Field KEY; static { Method _destroy = null; Field _key = null; try { Method destroy = SecretKeySpec.class.getMethod("destroy"); SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); destroy.invoke(key); _destroy = destroy; } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { } try { _key = SecretKeySpec.class.getDeclaredField("key"); _key.setAccessible(true); } catch (NoSuchFieldException | SecurityException ex) { } DESTROY = _destroy; KEY = _key; } static void close(SecretKeySpec secretKeySpec) { if (secretKeySpec != null) { if (DESTROY != null) { try { DESTROY.invoke(secretKeySpec); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new IllegalStateException("inconceivable: " + ex); } } else if (KEY != null) { try { byte[] key = (byte[]) KEY.get(secretKeySpec); Arrays.fill(key, (byte) 0); KEY.set(secretKeySpec, null); } catch (IllegalAccessException | IllegalArgumentException ex) { throw new IllegalStateException("inconceivable: " + ex); } } } } public final SecretKeySpec secretKeySpec; CloseableKey(SecretKeySpec _secretKeySpec) { secretKeySpec = _secretKeySpec; } @Override public void close() { close(secretKeySpec); } }

La forma de usar esto es como

try (CloseableKey key = new CloseableKey(new SecretKeySpec(data, 0, 16, "AES"))) { aesecb.init(Cipher.ENCRYPT_MODE, key.secretKeySpec); }

Uso la interfaz de Closeable porque Destroyable es solo una característica de 1.8+. Esta versión funciona en 1.7+ y es bastante eficiente (hace una prueba de destrucción en una clave para decidir volver a usarla).


Tomando una táctica ligeramente diferente, una vez que haya identificado el área correcta de memoria para sobrescribir, es posible que desee hacerlo más de una vez:

zerorize(SecretKey key) { byte[] rawKey = key.getEncoded(); Arrays.fill(rawKey, (byte) 0xFF); Arrays.fill(rawKey, (byte) 0xAA); Arrays.fill(rawKey, (byte) 0x55); Arrays.fill(rawKey, (byte) 0x00); }


getEncoded() parece devolver principalmente un clon de la clave (desde el origen de Oracle 1.6 de javax.security.auth.kerberos por ejemplo):

public final byte[] getEncoded() { if (destroyed) throw new IllegalStateException("This key is no longer valid"); return (byte[])keyBytes.clone(); }

por lo tanto, borrar los datos de retorno no borra todas las copias de la clave de la memoria.

La única forma de borrar la clave de SecretKey es SecretKey a javax.security.auth.Destroyable si implementa la interfaz e invoca el método destroy() :

public void destroy() throws DestroyFailedException { if (!destroyed) { destroyed = true; Arrays.fill(keyBytes, (byte) 0); } }

Por extraño que parezca, parece que todas las implementaciones clave no implementan javax.security.auth.Destroyable . com.sun.crypto.provider.DESedeKey no usa ni javax.crypto.spec.SecretKeySpec para AES. Ambas implementaciones clave también clonan la clave en el método getEncoded . ¿Entonces parece que para estos algoritmos muy comunes 3DES y AES no tenemos forma de limpiar la memoria de la clave secreta?