Java 9: производительность AES-GCM

Я выполнил простой тест для измерения производительности AES-GCM в Java 9, зашифровав байтовые буферы в цикле. Результаты были несколько запутанными. Кажется, работает родное (аппаратное) ускорение - но не всегда. Более конкретно,

  1. При шифровании буферов 1 МБ в цикле скорость составляет ~ 60 МБ/с в течение первых ~ 50 секунд. Затем он прыгает до 1100 МБ/с и остается там. Решает ли JVM активировать аппаратное ускорение через 50 секунд (или 3 ГБ данных)? это можно настроить? Где я могу прочитать о новой реализации AES-GCM (кроме здесь).
  2. При шифровании 100 МБ буферов аппаратное ускорение вообще не срабатывает. Скорость ровная 60 МБ/с.

Мой тестовый код выглядит так:

int plen = 1024*1024;
byte[] input = new byte[plen];
for (int i=0; i < input.length; i++) { input[i] = (byte)i;}
byte[] nonce = new byte[12];
...
// Uses SunJCE provider
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] key_code = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
SecretKey key = new SecretKeySpec(key_code, "AES");
SecureRandom random = new SecureRandom();

long total = 0;
while (true) {
  random.nextBytes(nonce);
  GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
  cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  byte[] cipherText = cipher.doFinal(input);
  total += plen;
  // print delta_total/delta_time, once in a while
}

Обновление за февраль 2019 года: HotSpot был изменен для решения этой проблемы. Исправление применяется в Java 13, а также перенесено в Java 11 и 12.

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8201633, https://hg.openjdk.java.net/jdk/jdk/rev/f35a8aaabcb9

Обновление от 16 июля 2019 года. Недавно выпущенная версия Java (Java 11.0.4) устраняет эту проблему.

Ответ 1

Спасибо @Holger за указание в правильном направлении. cipher.doFinal с несколькими cipher.update вызовет аппаратное ускорение почти сразу.

Основываясь на этой ссылке, GCM Analysis, я использую 4KB куски в каждом обновлении. Теперь и буферы 1 МБ и 100 МБ шифруются со скоростью 1100 МБ/с (через несколько десятков миллисекунд).

Решение заключается в замене

byte[] cipherText = cipher.doFinal(input);

с

int clen = plen + GCM_TAG_LENGTH;
byte[] cipherText = new byte[clen];

int chunkLen = 4 * 1024;
int left = plen;
int inputOffset = 0;
int outputOffset = 0;

while (left > chunkLen) {
  int written = cipher.update(input, inputOffset, chunkLen, cipherText, outputOffset);
  inputOffset += chunkLen;
  outputOffset += written;
  left -= chunkLen;
}

cipher.doFinal(input, inputOffset, left, cipherText, outputOffset);

Ответ 2

Несколько обновлений по этой проблеме.

  1. Java 10, выпущенная в конце марта, имеет ту же проблему, которая может быть обойдена одним и тем же обходным путем - только для шифрования данных.

  2. Обходной путь в основном не работает для дешифрования данных - как в Java 9, так и в Java 10.

Я отправил отчет об ошибке на платформу Java. Он был оценен и опубликован как JDK-8201633.

Ответ 4

Версия Java, выпущенная 16 июля 2019 года (Java 11.0.4), исправляет эту проблему.