¿Cómo puedo cero-ise una clave secreta en java?

¿Es suficiente el siguiente código java para borrar la clave secreta en la memoria (estableciendo 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?

8 Solutions collect form web for “¿Cómo puedo cero-ise una clave secreta en java?”

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

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

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

Por lo tanto limpiando los datos de vuelta no borra todas las copias de la llave de la memoria.

La única manera de limpiar la clave de la SecretKey es lanzarla 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); } } 

Curiosamente parece que todas las implementaciones clave no implementan javax.security.auth.Destroyable . javax.crypto.spec.SecretKeySpec ni javax.crypto.spec.SecretKeySpec no se utiliza para AES. Ambas implementaciones clave también clonan la clave en el método getEncoded . Así que parece que para estos algoritmos muy comunes 3DES y AES no tenemos una manera de limpiar la memoria de la clave secreta?

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

No creo que haya una manera en general para borrar los datos en un SecretKey. Clases de implementación específicas pueden proporcionar para eso, pero no soy consciente de cualquiera que lo haga. En Android, el riesgo de dejar sin datos los datos 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 raíz puede tomar instantáneas de memoria y enviarlas a algún superordenador en algún lugar para su análisis, con la esperanza de descubrir las claves secretas de alguien. Pero nunca he oído hablar de un ataque de este tipo, y me parece que no es competitivo con otras formas de acceder a un sistema. ¿Existe alguna razón por la que esté preocupado por esta particular vulnerabilidad hipotética?

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

En palabras más cortas: si su modelo de seguridad y su contexto requieren claves de reducción a cero, entonces no debería usar Java en absoluto (o casi cualquier cosa excepto C y ensamblado).

GetEncoded devuelve una copia de la clave secreta (para borrar que no tiene efecto sobre los datos de clave secreta) y destruir por defecto lanza DestroyFailedException que es peor que inútil. También está disponible sólo en 1.8+ para que Android no tenga suerte. Aquí hay un hack que usa la introspección para (1) invocar destruir si está disponible y no lanzar una excepción, de lo contrario (2) cero los datos clave y establecer la referencia a null.

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

Utilizo la interfaz de Closeable porque Destroyable es una característica de 1.8+ solamente. Esta versión funciona en 1.7+ y es bastante eficiente (hace que un ensayo destruya en una llave para decidir utilizarla nunca otra vez).

Excepto valores primitivos, todo lo demás en Java siempre se pasa por referencia, incluyendo arrays, así que sí, está borrando la matriz de bytes dada correctamente.

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

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 se hace el Array.fill depende de si la clave es respaldada por la matriz devuelta. 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.

Es fácil de encontrar sin embargo. Pruebe lo siguiente:

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

Si la salida es false , usted sabe que la clave todavía se almacena en SecretKey .

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); } 
  • Android - Almacenamiento seguro
  • Proveedor de contenido privado de Android?
  • Ofuscación: ocultar valores codificados en java
  • ¿Cuál es la mejor manera de ocultar la contraseña de keystore en Android?
  • Android - ¿Cómo se trata 9774d56d682e549c? ID de Android
  • Asegurar cadena en APK
  • Comprobar si existe URL o no en Servidor
  • Seguridad de SharedPreference de Android
  • ¿Hay análogos javax.smartcardio en Android?
  • ¿Cómo funciona la autenticación de WhatsApp?
  • Clave de String en Java RSA
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.