cryptography - Descifrado y descifrado AES GCM lento con Java 8u20
benchmarking java-8 (3)
Dejando de lado el micro-benchmarking, el rendimiento de la implementación de GCM en JDK 8 (al menos hasta 1.8.0_25) está paralizado.
Puedo reproducir constantemente los 3MB / s (en una computadora portátil Haswell i7) con un micro-benchmark más maduro.
Desde una inmersión de código , esto parece deberse a una implementación multiplicadora ingenua y no a una aceleración de hardware para los cálculos de GCM.
En comparación, AES (en modo ECB o CBC) en JDK 8 usa un intrínseco acelerado AES-NI y es (para Java al menos) muy rápido (en el orden de 1GB / s en el mismo hardware), pero el AES / GCM general El rendimiento está completamente dominado por el rendimiento roto de GCM.
Hay planes para implementar la aceleración de hardware , y ha habido presentaciones de terceros para mejorar el rendimiento , pero aún no han llegado a una versión.
Otra cosa a tener en cuenta es que la implementación de JDK GCM también amortigua todo el texto plano en el descifrado hasta que se verifica la etiqueta de autenticación al final del texto cifrado, lo que la paraliza para usarla con mensajes grandes.
Bouncy Castle tiene (al momento de escribir) implementaciones GCM más rápidas (y OCB si está escribiendo software de código abierto que no esté gravado por las leyes de patentes de software).
Actualizado julio 2015 - 1.8.0_45 y JDK 9
JDK 8+ obtendrá una implementación Java mejorada (y de tiempo constante) (aportada por Florian Weimer de RedHat): esto ha aterrizado en las compilaciones JDK 9 EA, pero aparentemente aún no está en 1.8.0_45. JDK9 (al menos EA b72) también tiene intrínsecos GCM: la velocidad AES / GCM en b72 es 18MB / s sin intrínsecos habilitados y 25MB / s con intrínsecos habilitados, ambos de los cuales son decepcionantes. la implementación es ~ 60MB / sy la más lenta (tiempo constante, no totalmente optimizado) es ~ 26MB / s.
Actualizado enero 2016 - 1.8.0_72:
Algunas correcciones de rendimiento aterrizaron en JDK 1.8.0_60 y el rendimiento en el mismo punto de referencia ahora es de 18 MB / s, una mejora 6x respecto del original, pero aún mucho más lento que las implementaciones de BC.
Estoy tratando de cifrar y descifrar datos utilizando AES / GCM / NoPadding. Instalé los archivos de política de fuerza ilimitada de JCE y ejecuté el punto de referencia (de mentalidad simple) a continuación. He hecho lo mismo con OpenSSL y pude lograr más de 1 GB / s de cifrado y descifrado en mi PC.
Con el punto de referencia a continuación, solo puedo obtener encriptación y descifrado de 3 MB / s utilizando Java 8 en la misma PC. ¿Alguna idea de lo que estoy haciendo mal?
public static void main(String[] args) throws Exception {
final byte[] data = new byte[64 * 1024];
final byte[] encrypted = new byte[64 * 1024];
final byte[] key = new byte[32];
final byte[] iv = new byte[12];
final Random random = new Random(1);
random.nextBytes(data);
random.nextBytes(key);
random.nextBytes(iv);
System.out.println("Benchmarking AES-256 GCM encryption for 10 seconds");
long javaEncryptInputBytes = 0;
long javaEncryptStartTime = System.currentTimeMillis();
final Cipher javaAES256 = Cipher.getInstance("AES/GCM/NoPadding");
byte[] tag = new byte[16];
long encryptInitTime = 0L;
long encryptUpdate1Time = 0L;
long encryptDoFinalTime = 0L;
while (System.currentTimeMillis() - javaEncryptStartTime < 10000) {
random.nextBytes(iv);
long n1 = System.nanoTime();
javaAES256.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(16 * Byte.SIZE, iv));
long n2 = System.nanoTime();
javaAES256.update(data, 0, data.length, encrypted, 0);
long n3 = System.nanoTime();
javaAES256.doFinal(tag, 0);
long n4 = System.nanoTime();
javaEncryptInputBytes += data.length;
encryptInitTime = n2 - n1;
encryptUpdate1Time = n3 - n2;
encryptDoFinalTime = n4 - n3;
}
long javaEncryptEndTime = System.currentTimeMillis();
System.out.println("Time init (ns): " + encryptInitTime);
System.out.println("Time update (ns): " + encryptUpdate1Time);
System.out.println("Time do final (ns): " + encryptDoFinalTime);
System.out.println("Java calculated at " + (javaEncryptInputBytes / 1024 / 1024 / ((javaEncryptEndTime - javaEncryptStartTime) / 1000)) + " MB/s");
System.out.println("Benchmarking AES-256 GCM decryption for 10 seconds");
long javaDecryptInputBytes = 0;
long javaDecryptStartTime = System.currentTimeMillis();
final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * Byte.SIZE, iv);
final SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
long decryptInitTime = 0L;
long decryptUpdate1Time = 0L;
long decryptUpdate2Time = 0L;
long decryptDoFinalTime = 0L;
while (System.currentTimeMillis() - javaDecryptStartTime < 10000) {
long n1 = System.nanoTime();
javaAES256.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
long n2 = System.nanoTime();
int offset = javaAES256.update(encrypted, 0, encrypted.length, data, 0);
long n3 = System.nanoTime();
javaAES256.update(tag, 0, tag.length, data, offset);
long n4 = System.nanoTime();
javaAES256.doFinal(data, offset);
long n5 = System.nanoTime();
javaDecryptInputBytes += data.length;
decryptInitTime += n2 - n1;
decryptUpdate1Time += n3 - n2;
decryptUpdate2Time += n4 - n3;
decryptDoFinalTime += n5 - n4;
}
long javaDecryptEndTime = System.currentTimeMillis();
System.out.println("Time init (ns): " + decryptInitTime);
System.out.println("Time update 1 (ns): " + decryptUpdate1Time);
System.out.println("Time update 2 (ns): " + decryptUpdate2Time);
System.out.println("Time do final (ns): " + decryptDoFinalTime);
System.out.println("Total bytes processed: " + javaDecryptInputBytes);
System.out.println("Java calculated at " + (javaDecryptInputBytes / 1024 / 1024 / ((javaDecryptEndTime - javaDecryptStartTime) / 1000)) + " MB/s");
}
EDITAR: Lo dejo como un ejercicio divertido para mejorar este punto de referencia de mente simple.
He probado un poco más utilizando el ServerVM, eliminé las llamadas de nanoTime e introduje el calentamiento, pero como esperaba, nada de esto mejoró los resultados de referencia. Es de líneas planas a 3 megabytes por segundo.
Esto ahora se ha abordado parcialmente en Java 8u60 con JDK-8069072 . Sin este arreglo obtengo 2.5M / s. Con este arreglo consigo 25M / s. Deshabilitar GCM completamente me da 60M / s.
Para deshabilitar GCM, cree completamente un archivo llamado java.security
con la siguiente línea:
jdk.tls.disabledAlgorithms=SSLv3,GCM
Luego comienza tu proceso Java con:
java -Djava.security.properties=/path/to/my/java.security ...
Si esto no funciona, es posible que deba habilitar el reemplazo de propiedades de seguridad editando /usr/java/default/jre/lib/security/java.security
(la ruta real puede ser diferente según el sistema operativo) y agregando:
policy.allowSystemProperty=true
La implementación de OpenSSL está optimizada por la rutina de ensamblaje utilizando la instrucción pclmulqdq (plataforma x86). Es muy rápido debido al algoritmo paralelo.
La implementación de java es lenta. pero también se optimizó en Hotspot usando la rutina de ensamblaje (no en paralelo). Tienes que calentar el jvm para usar Hotspot intrinsic. El valor predeterminado de -XX: CompileThreshold es 10000.
// pseudocódigo
warmUp_GCM_cipher_loop10000_times ();
do_benchmark ();