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).
-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
@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. –
@Joachim ... Me refería a "la matriz de bytes dada", aunque aclaró que podría haber otra copia. –