2014-09-23 16 views
20

Sto tentando di crittografare e decodificare i dati utilizzando AES/GCM/NoPadding. Ho installato JCE Unlimited Strength Policy Files e ho seguito il benchmark (semplice) di seguito. Ho fatto lo stesso utilizzando OpenSSL ed è stato in grado di ottenere oltre il 1 GB/s crittografia e decrittografia sul mio PC.Lenta crittografia e decrittografia AES GCM con Java 8u20

Con il benchmark di seguito sono in grado di ottenere solo la crittografia e la decrittografia di 3 MB/s utilizzando Java 8 sullo stesso PC. Qualche idea su cosa sto facendo male?

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

EDIT: lascio come un esercizio divertente per migliorare questo semplice punto di riferimento mentalità.

Ho provato un po 'di più utilizzando ServerVM, rimosso le chiamate nanoTime e introdotto il riscaldamento, ma, come mi aspettavo, nessuna di queste ha avuto alcun miglioramento sui risultati del benchmark. È piatta a 3 megabyte al secondo.

+5

In primo luogo, il benchmarking è sbagliato: nessun riscaldamento, singola iterazione, chiamate nanoTime eccessive. Le proprietà intrinseche di Hotspot per AES-NI sono utilizzate solo con un compilatore JIT ottimizzato, devi raggiungerlo prima di valutare le prestazioni. Secondo, prova AES/CBC. Misurate effettivamente aes-gcm con OpenSSL e vi ha dato 1 GB/s? –

+1

Si noti inoltre che per utilizzare intrinseche AES-NI è necessario utilizzare la Server VM, una moderna CPU Intel con supporto, * e * per avere una sequenza di riscaldamento. Notare che OpenSSL è una delle librerie più veloci là fuori, il codice byte potrebbe essere relativamente veloce per la logica di business, ma per la crittografia si * vedranno differenze con librerie C/C++ ben implementate. –

+0

Sì, lo so che questo non è il benchmark più robusto, ma 3 MB/s contro 1 GB/s è ancora molto significativo e ritengo che questo benchmark semplice sia abbastanza buono da portare il punto. Ho provato AES/CBC e sono in grado di ottenere più di 400 MB/s per la crittografia e più di 1 GB/s per la decrittografia utilizzando il codice Java. – Christo

risposta

18

Micro-benchmarking a parte, le prestazioni dell'implementazione GCM in JDK 8 (almeno fino a 1.8.0_25) sono azzoppate.

Posso riprodurre in modo coerente i 3MB/s (su un laptop Haswell i7) con un micro-benchmark più maturo.

Da un code dive, questo sembra essere dovuto a un'implementazione del moltiplicatore ingenuo e nessuna accelerazione hardware per i calcoli GCM.

AES confronto (in modalità ECB o CBC) in JDK 8 utilizza un intrinseco AES-NI accelerato ed è (per Java almeno) molto veloce (nell'ordine di 1 GB/s sullo stesso hardware), ma il le prestazioni complessive di AES/GCM sono completamente dominate dalle prestazioni GCM rotte.

Ci sono plans to implement hardware acceleration e there have been third party submissions to improve the performance with, ma questi non sono ancora stati rilasciati.

Un'altra cosa da tenere presente è che l'implementazione di JDK GCM memorizza anche l'intero testo in chiaro sulla decrittografia finché il tag di autenticazione alla fine del testo cifrato non viene verificato, il che lo paralizza per l'uso con messaggi di grandi dimensioni.

Bouncy Castle ha (al momento della scrittura) implementazioni GCM più veloci (e OCB se si sta scrivendo software open source di non gravato da leggi sui brevetti software).


Aggiornato luglio 2015 - 1.8.0_45 e JDK 9

JDK 8+ riceveranno un (tempo e costante) è migliorato implementazione Java (contributo di Florian Weimer di RedHat) - questo è sbarcato in JDK 9 Build EA, ma apparentemente non ancora in 1.8.0_45. JDK9 (dal momento che EA b72 almeno) ha anche intrinseco GCM - La velocità AES/GCM su b72 è 18MB/s senza intrinseca abilitata e 25MB/s con intrinseca abilitata, entrambi dei quali sono deludenti - per il confronto il più veloce (tempo non costante) L'implementazione BC è ~ 60 MB/se il più lento (tempo costante, non completamente ottimizzato) è ~ 26 MB/s.


Aggiornamento gennaio 2016 - 1.8.0_72:

Alcune correzioni di prestazioni sono finite in JDK 1.8.0_60 e le prestazioni sullo stesso benchmark ora sono 18MB/s - un miglioramento di 6 volte rispetto all'originale, ma ancora molto più lento delle implementazioni BC.

+0

GCM on bouncy memorizza nella cache le dimensioni del tag durante la decodifica (dato che è usato come parte del testo cifrato), anche se sto provando un nuovo codice per ottenerlo (e richiedere il tag separatamente, come dovrebbe essere). Riscrivere l'esponenziazione richiesta per consentire l'aggiunta dell'AAD in un secondo momento è un bel problema. –

+0

Qualche aggiornamento su Java 8? – kellyfj

+0

Ho eseguito alcuni test per crittografare/decrittografare 84 file. Implemtention JDK: crypt = 15 secondi/decrypt = 111 secondi. BC: crypt = 14 seconds/decrypt = 23 seconds !!!Com'è possibile che la decodifica sia così lenta con il JDK nativo rispetto a BC? BC è solo un file jar contenente solo file di classe (quindi file di linguaggio java). Oracle (sole) ha accesso al sistema operativo nativo con JRE/JDK, in modo che possano implementare tutte queste cose in linguaggio di basso livello e più veloce e ottimizzato il loro codice (come in C)! È incomprensibile per me:/ – Alexxx

3

Questo problema è stato parzialmente risolto in Java 8u60 con JDK-8069072. Senza questa correzione ottengo 2,5 M/s. Con questa correzione ottengo 25M/s. Disabilitare GCM mi dà completamente 60M/s.

Per disabilitare GCM creare completamente un file denominato java.security con la seguente riga:

jdk.tls.disabledAlgorithms=SSLv3,GCM 

quindi avviare il processo di Java con:

java -Djava.security.properties=/path/to/my/java.security ... 

Se questo non funziona, potrebbe essere necessario abilitare l'override delle proprietà di sicurezza modificando /usr/java/default/jre/lib/security/java.security (il percorso effettivo potrebbe essere diverso a seconda del sistema operativo) e aggiungendo:

policy.allowSystemProperty=true 
+0

Ho aperto java.security principale e cambiato securerandom.source = file:/dev/random -> securerandom.source = file:/dev/urandom. E mi ha aiutato per me – demaksee

+0

Vedi qui per alcuni dettagli su random/urandom http://stackoverflow.com/questions/21757653/cipher-getinstance-is-too-slow#comment32942702_21757854 – demaksee

0

L'implementazione di OpenSSL è ottimizzata dal assembly routine utilizzando l'istruzione pclmulqdq (piattaforma x86). È molto veloce grazie all'algoritmo in parallelo.

L'implementazione java è lenta. ma è stato anche ottimizzato in Hotspot usando la routine assembly (non parallela). devi scaldare la jvm per usare l'Hotspot intrinseco. Il valore predefinito di XX: CompileThreshold è 10000.

// pseudocodice

warmUp_GCM_cipher_loop10000_times();

do_benchmark();