Java AES-GCM очень медленный по сравнению с AES-CTR - PullRequest
0 голосов
/ 13 февраля 2019

Рассмотрим следующий код:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;

public class AES_Mod_Speed {
    // AES parameters
    private static final int AES_KEY_SIZE = 128; // in bits
    private static final int AES_COUNTER_SIZE = 16; // in bytes
    private static final int GCM_NONCE_LENGTH = 12; // in bytes. 12 is the recommended value.
    private static final int GCM_TAG_LENGTH = 16 * 8; // in bits

    public static void main(String[] args) throws Exception {
        SecureRandom sr = new SecureRandom();

        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(AES_KEY_SIZE);
        SecretKey key = kg.generateKey();

        byte[] counter = new byte[AES_COUNTER_SIZE];
        Cipher aes_ctr = Cipher.getInstance("AES/CTR/NoPadding");

        byte[] nonce = new byte[GCM_NONCE_LENGTH];
        Cipher aes_gcm = Cipher.getInstance("AES/GCM/NoPadding");

        for (int i = 0; i < 10; i++) {
            sr.nextBytes(counter);
            aes_ctr.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(counter));
            speedTest(aes_ctr);
        }

        System.out.println("-----------------------------------------");

        for (int i = 0; i < 10; i++) {
            sr.nextBytes(nonce);
            aes_gcm.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH, nonce));
            speedTest(aes_gcm);
        }

    }

    private static void speedTest(Cipher cipher) throws Exception {
        byte[] ptxt = new byte[1 << 26];
        long start, end;

        start = System.nanoTime();
        cipher.doFinal(ptxt);
        end = System.nanoTime();


        System.out.printf("%s took %f seconds.\n",
                cipher.getAlgorithm(),
                (end - start) / 1E9);
    }
}

Результат (Java 11.0.2):


AES/CTR/NoPadding took 0.259894 seconds.
AES/CTR/NoPadding took 0.206136 seconds.
AES/CTR/NoPadding took 0.247764 seconds.
AES/CTR/NoPadding took 0.196413 seconds.
AES/CTR/NoPadding took 0.181117 seconds.
AES/CTR/NoPadding took 0.194041 seconds.
AES/CTR/NoPadding took 0.181889 seconds.
AES/CTR/NoPadding took 0.180970 seconds.
AES/CTR/NoPadding took 0.180546 seconds.
AES/CTR/NoPadding took 0.179797 seconds.
-----------------------------------------
AES/GCM/NoPadding took 0.961051 seconds.
AES/GCM/NoPadding took 0.952866 seconds.
AES/GCM/NoPadding took 0.963486 seconds.
AES/GCM/NoPadding took 0.963280 seconds.
AES/GCM/NoPadding took 0.961424 seconds.
AES/GCM/NoPadding took 0.977850 seconds.
AES/GCM/NoPadding took 0.961449 seconds.
AES/GCM/NoPadding took 0.957542 seconds.
AES/GCM/NoPadding took 0.967129 seconds.
AES/GCM/NoPadding took 0.959292 seconds.

Это странно, поскольку GCM почти в пять раз медленнее, чем CTR (для шифрования 1<<26 байт, т.е. 64 MB).Используя тест скорости через OpenSSL 1.1.1a, я ввел команды openssl speed -evp aes-128-ctr и openssl speed -evp aes-128-gcm и получил следующие результаты:

The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
aes-128-ctr     463059.16k  1446320.32k  3515070.12k  5182218.92k  6063797.59k  6210150.19k
aes-128-gcm     480296.99k  1088337.47k  2531854.17k  4501395.11k  5940079.27k  6087589.89k

Видно, что GCM лишь немного медленнее, чем CTR, особеннодля больших открытых текстов.

Почему реализация AES-GCM в Java медленнее, чем в AES-CTR?Я что-то упустил?

PS: Я использовал Java JMH для микробенчмаркинга, и результаты были похожи.

Пожалуйста, смотрите также thisответ , где OP объясняет, как проблемы производительности AES были решены в более ранних версиях JDK.

1 Ответ

0 голосов
/ 13 февраля 2019

Здесь та же проблема, что и в этом ответе .

Метод шифрования вызывается недостаточно раз, чтобы скомпилировать JIT.То, что вы видите, является результатом чисто интерпретированного исполнения.Попробуйте измерить больше итераций шифрования меньших массивов.Или просто добавьте фиктивный цикл для «прогрева» компилятора.

Например, вставьте следующий цикл перед основным циклом тестирования.Он будет выполнять doFinal достаточное количество раз, чтобы обеспечить его компиляцию.

    // Warm-up
    for (int i = 0; i < 100000; i++) {
        sr.nextBytes(nonce);
        aes_gcm.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH, nonce));
        aes_gcm.doFinal(new byte[16]);
    }

Как только компилятор JIT выполнит свою работу, результаты следующего теста будут намного лучше.Фактически, ключевые методы шифрования AES встроенные в JDK;HotSpot JVM имеет специальную реализацию для них, написанную в оптимизированной сборке с набором инструкций AVX и AES-NI.

На моем ноутбуке тест после прогрева стал на порядок быстрее:

AES/GCM/NoPadding took 0.108993 seconds.
AES/GCM/NoPadding took 0.089832 seconds.
AES/GCM/NoPadding took 0.063606 seconds.
AES/GCM/NoPadding took 0.061044 seconds.
AES/GCM/NoPadding took 0.073603 seconds.
AES/GCM/NoPadding took 0.063733 seconds.
AES/GCM/NoPadding took 0.058680 seconds.
AES/GCM/NoPadding took 0.058996 seconds.
AES/GCM/NoPadding took 0.058327 seconds.
AES/GCM/NoPadding took 0.058664 seconds.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...