2011-08-25 10 views
6

¿El siguiente código Java es suficiente para borrar la clave secreta en la memoria (configurando todo su valor de byte en 0)?¿Cómo cero una clave secreta en java?

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

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

Respuesta

-1

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

key.getEncoded() devolverá una referenciaa una matriz.

Si el contenido de la clave se descarta cuando hace lo Array.fill depende de si o no la clave está respaldado 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 es y no 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 la salida es false, usted sabe que la llave aún se guarda en SecretKey.

-1

Excepto los valores primitivos, todo lo demás en Java se pasa siempre 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 aún 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.

+4

-1: * todo lo demás en Java se pasa siempre por referencia * - Nooo, ¡Java * siempre * pasa por valor! La razón por la que no puede pasar * un objeto * por valor es porque ninguna variable puede contener un objeto en primer lugar. – aioobe

+0

@aioobe ... ¿Seguro que estamos hablando de la misma Java? int se pasa por valor, boolean se pasa por valor, Integer es una referencia, así como cualquier objeto, matriz, etc ... Java pasa "un valor", que en realidad es una "referencia" a un objeto, por lo que es por referencia. –

+0

@Joachim ... Me refería a "la matriz de bytes dada", aunque aclaró que podría haber otra copia. –

0

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

No creo que haya una manera en general de borrar los datos en un SecretKey. Las clases de implementación específicas pueden ser, 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 donde 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?

6

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

+0

Solo funciona en 1.8+ y generalmente solo arroja DestroyFailedException –

-2

Tomando una perspectiva un poco 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); 
} 
0

Dependiendo de la tecnología capaz de alimentar el recolector de basura, un solo el objeto 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 a "la" matriz que contiene la clave, y no a copia de eso

En palabras cortas: si su modelo de seguridad y contexto requieren claves de puesta a cero, entonces no debería usar Java (o cualquier cosa menos C y ensamblaje).

+0

Pero si tiene que usar Java, ponga a cero el sistema rápidamente, antes de que el GC pueda volver a empaquetar datos o el SO lo mueva para intercambiar, etc. –

3

getEncoded() parece volver sobre todo un clon de la llave (de la fuente 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, limpiar los datos de retorno no borra todas las copias de la llave de memoria.

La única manera de eliminar la clave de la SecretKey es echarlo a los javax.security.auth.Destroyable si se implementa la interfaz e invocar el destroy() método:

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

Curiosamente parece que todo clave de implementación no implementan javax.security.auth.Destroyable . com.sun.crypto.provider.DESedeKey no lo hace ni se usa 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?

1

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 manera de utilizar esto es como

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

utilizo la interfaz que se puede cerrar, porque Destroyable sólo es una característica 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).

+0

Esto es un truco, y el GC puede volver a empaquetar la memoria o el sistema operativo mover los datos al intercambio, lo que puede provocar la pérdida de datos clave. Cierre la llave tan pronto como sea posible para minimizar la posibilidad de fugas debido a un efecto secundario de GC u OS. –

Cuestiones relacionadas