Подтвердить что ты не робот

Медленное шифрование и дешифрование AES GCM с помощью Java 8u20

Я пытаюсь зашифровать и расшифровать данные с помощью AES/GCM/NoPadding. Я установил файлы политики защиты неограниченной силы (JCE Unlimited Strength) и выполнил (простой подход) ниже. Я сделал то же самое с использованием OpenSSL и смог добиться более чем 1 ГБ/с шифрования и дешифрования на моем ПК.

С приведенным ниже эталоном я могу получить только 3 МБ/с шифрование и дешифрование с использованием Java 8 на одном ПК. Любая идея, что я делаю неправильно?

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: Я оставляю это как забавное упражнение, чтобы улучшить этот простой мысленный тест.

Я тестировал еще несколько версий ServerVM, удалял вызовы nanoTime и вводил разминку, но, как я ожидал, ничто из этого не улучшило результаты тестов. Он плоский с размером в 3 мегабайт в секунду.

4b9b3361

Ответ 1

Микро-бенчмаркинг в сторону, производительность реализации GCM в JDK 8 (по крайней мере до 1,8,0_25) искалечена.

Я могу последовательно воспроизводить 3 МБ/с (на ноутбуке Haswell i7) с более зрелым микро-бенчмарком.

Из кодового погружения, это, по-видимому, связано с реализацией наивного множителя и без аппаратного ускорения для вычислений GCM.

По сравнению с AES (в режиме ECB или CBC) в JDK 8 используется ускоренная внутренняя среда AES-NI и (по крайней мере, для Java) очень быстрая (порядка 1 ГБ/с на одном и том же оборудовании), но общая Производительность AES/GCM полностью подчинена нарушенной производительности GCM.

Есть планирует реализовать аппаратное ускорение, а было третье чтобы улучшить производительность с помощью, но пока не дошли до релиза.

Что-то еще, о чем следует знать, это то, что реализация JMK GCM также выполняет буферизацию всего открытого текста при расшифровке до тех пор, пока не будет проверен тег аутентификации в конце зашифрованного текста, который калечит его для использования с большими сообщениями.

У Bouncy Castle есть (на момент написания) более быстрые реализации GCM (и OCB, если вы пишете программное обеспечение с открытым исходным кодом, не обремененное патентными законами программного обеспечения).


Обновлен июль 2015 г. - 1.8.0_45 и JDK 9

JDK 8+ получит улучшенную (и постоянную) реализацию Java (внесенный Флорианом Веймером из RedHat) - это приземлилось в сборке JDK 9 EA, но, видимо, еще не в 1.8.0_45. JDK9 (так как EA b72 по крайней мере) также имеет встроенные функции GCM - скорость AES/GCM на b72 составляет 18 Мбайт/с без встроенных функций и 25 МБ/с с включенными встроенными функциями, обе из которых являются разочаровывающими - для сравнения самое быстрое (не постоянное время) ВС реализация составляет ~ 60 МБ/с, а самый медленный (постоянное время, не полностью оптимизированное) составляет ~ 26 МБ/с.


Обновлено Январь 2016 - 1.8.0_72:

Некоторые исправления производительности помещены в JDK 1.8.0_60, а производительность в том же тесте составляет 18 Мбайт/с - это 6-кратное улучшение от оригинала, но все еще намного медленнее, чем реализации BC.

Ответ 2

Теперь это частично описано в Java 8u60 с JDK-8069072. Без этого исправления я получаю 2.5M/s. С этим исправлением я получаю 25M/s. Отключение GCM полностью дает мне 60 м/с.

Чтобы отключить GCM, полностью создайте файл с именем java.security со следующей строкой:

jdk.tls.disabledAlgorithms=SSLv3,GCM

Затем запустите свой Java-процесс с помощью

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

Если это не сработает, возможно, вам потребуется включить переопределение свойств безопасности, отредактировав /usr/java/default/jre/lib/security/java.security (фактический путь может отличаться в зависимости от ОС) и добавить:

policy.allowSystemProperty=true

Ответ 3

Реализация OpenSSL оптимизируется с помощью процедуры с помощью инструкции pclmulqdq (платформа x86). Это очень быстро из-за параллельного алгоритма.

Java-реализация выполняется медленно. но он также был оптимизирован в Hotspot с использованием процедуры сборки (не параллельной). вам нужно разогреть jvm, чтобы использовать встроенную Hotspot. Значение по умолчанию -XX: CompileThreshold равно 10000.

//псевдокод

warmUp_GCM_cipher_loop10000_times();

do_benchmark();