Почему для помещения тега аутентификации GCM в конец потока шифрования требуется внутренняя буферизация во время дешифрования?

В Java провайдер AES / GCM по умолчанию SunJCE будет - во время процесса дешифрования - внутренне буферизовать 1)зашифрованные байты, используемые в качестве входных данных или 2)дешифрованные байты, полученные в результате, Код приложения, выполняющий расшифровку, заметит, чтоCipher.update(byte[]) вернуть пустой байтовый массив иCipher.update(ByteBuffer, ByteBuffer) вернуть записанную длину 0. Затем, когда процесс завершится,Cipher.doFinal() вернусьвсе декодированные байты.

Первый вопрос: какие байты буферизуются, номер 1 или номер 2 выше?

Я предполагаю, что буферизация происходит только во время дешифрования, а не шифрования, потому что, во-первых, проблемы, которые возникают из-за этой буферизации (кратко описано), не возникают в моем клиенте Java, выполняющем шифрование файлов, считываемых с диска, это всегда происходит на стороне сервера, получать эти файлы и делать расшифровку. Во-вторых, так сказаноВот, Судя только по собственному опыту, я не могу быть уверен, потому что мой клиент используетCipherOutputStream, Клиент явно не использует методы в экземпляре Cipher. Следовательно, я не могу определить, используется ли внутренняя буферизация или нет, потому что я не вижу, что возвращает метод update и final.

Мои настоящие проблемы возникают, когда зашифрованные файлы, которые я передаю от клиента к серверу, становятся большими. Я имею в виду более 100 МБ.

Затем происходит то, что Cipher.update () генерируетOutOfMemoryError, Очевидно, из-за внутреннего буфера растет и растет.

Кроме того, несмотря на внутреннюю буферизацию и отсутствие байтов результата, полученных от Cipher.update (),Cipher.getOutputSize (целое) постоянно сообщать о постоянно растущей целевой длине буфера. Следовательно, мой код приложения вынужден выделять постоянно растущийByteBuffer это подача в Cipher.update (ByteBuffer, ByteBuffer). Если я попытаюсь обмануть и передать в байтовый буфер с меньшей емкостью, то метод обновления выбрасываетShortBufferException # 1, Знание того, что я создаю огромные байтовые буферы без толку, довольно деморализует.

Учитывая, что внутренняя буферизация является корнем всего зла, то очевидное решение, которое я могу применить здесь, состоит в том, чтобы разбить файлы на куски, скажем, по 1 МБ каждый - у меня никогда не возникает проблем при отправке небольших файлов, только больших. Но я изо всех сил пытаюсь понять, почему внутренняя буферизация происходит в первую очередь.

Ранее связанныйТак ответь говорит, что тег аутентификации GCM: «добавляется в конец зашифрованного текста», но «его не нужно ставить в конце», и именно эта практика «портит онлайн-характер дешифрования GCM».

Почему размещение тега в конце только портит работу сервера по расшифровке?

Вот как мои рассуждения. Чтобы вычислить тег аутентификации или MAC, если хотите, клиент использует какую-то хеш-функцию. По-видимому,MessageDigest.update() не использует постоянно растущий внутренний буфер.

Тогда на принимающей стороне сервер не может сделать то же самое? Для начала он может расшифровать байты, хотя и не прошедшие проверку подлинности, передать их в свою функцию обновления алгоритма хеширования, а когда тег прибудет, завершить дайджест и проверить MAC, отправленный клиентом.

Я не крипто-парень, поэтому, пожалуйста, говорите со мной, как будто я и тупой, и сумасшедший, но достаточно любящий, чтобы заботиться о некоторых =) Я от всего сердца благодарю вас за время, потраченное на чтение этого вопроса и, возможно, даже пролить немного света!

ОБНОВЛЕНИЕ № 1

Я не использую AD (связанные данные).

ОБНОВЛЕНИЕ № 2

Написал программное обеспечение, демонстрирующее шифрование AES / GCM с использованием Java, а такжеБезопасный удаленный протокол (SRP) и передача двоичных файлов в Java EE. Клиентский интерфейс написан на JavaFX и может использоваться для динамического изменения конфигурации шифрования или отправки файлов с использованием кусков. В конце передачи файла представлена ​​некоторая статистика времени, использованного для передачи файла, и времени, которое сервер расшифровывает. В хранилище также есть документ с некоторыми моими собственными исследованиями, связанными с GCM и Java.

Наслаждаться:https://github.com/MartinanderssonDotcom/secure-login-file-transfer/

# 1

Интересно отметить, что если мой сервер, который выполняет расшифровку, не обрабатывает сам шифр, вместо этого он используетCipherInputStream, тогда OutOfMemoryError не выбрасывается. Вместо этого клиенту удается передать все байты по проводам, но где-то во время расшифровки поток запроса зависает на неопределенное время, и я вижу, что один поток Java (может быть одним и тем же потоком) полностью использует ядро ​​ЦП, все время оставляя файл включенным. диск недоступен и с сообщенным размером файла 0. Затем по истечении чрезвычайно длительного промежутка времениCloseable источник закрыт, и моему предложению catch удается перехватить IOException, вызванное: «javax.crypto.AEADBadTagException: ввод слишком короткий - тег тега».

Что делает эту ситуацию странной, так это то, что передача файлов меньшего размера работает безупречно с одним и тем же фрагментом кода, поэтому очевидно, что тег можно правильно проверить. Проблема должна иметь ту же основную причину, что и при явном использовании шифра, то есть постоянно растущий внутренний буфер. Я не могу отследить на сервере, сколько байтов было успешно прочитано / расшифровано, потому что, как только начинается чтение входного потока шифра, переупорядочивание компилятора или другие JIT-оптимизации заставляют все мои операторы журналирования испаряться. Они [очевидно] не казнены вообще.

Обратите внимание, чтоэтот проект GitHub и его связанныйСообщение блога говорит, что CipherInputStream не работает. Но тесты, предоставленные этим проектом, не являются для меня провальными при использовании Java 8u25 и поставщика SunJCE. И, как уже было сказано, у меня все работает, если я использую только небольшие файлы.

Ответы на вопрос(3)

Ваш ответ на вопрос