Моделирование потокового шифра с помощью AES / CTR

Я пишу сервер приложений и решил использовать AES128 / CTR / NoPadding для безопасных соединений, поскольку он считается достаточно безопасным без необходимости расширять байты до границы блока, и я подумал, что это хорошо подходит для TCP, который логически цельный поток.

Проблема в том, что Cipher.update () не возвращает зашифрованный блок до тех пор, пока у него не будет полного 16-байтового блока, потому что CTR в основном основан на блочном шифре, хотя имитирует потоковый шифр. Я должен читать данные из TCP-сокета и обрабатывать сообщения, как только они приходят, но я не могу получить самый последний блок, потому что он все еще накапливается и его размер меньше 16 байт. И я не могу просто ждать, потому что мы не знаем, когда будет отправлено следующее сообщение. Конечно, я мог бы вызвать Cipher.doFinal (), чтобы получить остаток, но это означало бы конец потока (соединение) и объект Cipher будет повторно инициализирован.

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

Обновить

Разрабатываю приложение для Android и оказалось, что это проблема ВМ Dalvik. Как указали Роберт и Моннан ниже, у Java SE нет этой проблемы, по крайней мере, с поставщиком по умолчанию. Думаю, мне придется написать класс-оболочку или изменить режим на CFB8, чтобы обойти эту проблему. (CTR8 не работал) Спасибо за все ответы!


person K J    schedule 30.04.2013    source источник
comment
Можете ли вы дополнить сообщения так, чтобы их длина была кратна 16? Это гарантирует, что вы всегда получите дешифруемый фрагмент.   -  person Chris Heald    schedule 30.04.2013
comment
@Chris: Спасибо за предложение, это может быть возможным обходным путем. Но я бы хотел избежать этого, если возможно, потому что это нежелательно с точки зрения эффективности сети, хотя может быть незначительным.   -  person K J    schedule 30.04.2013
comment
Вы можете просто вызвать это для массива, состоящего из нулей, чтобы получить поток ключей. Затем вставьте его вручную в свое сообщение.   -  person CodesInChaos    schedule 30.04.2013
comment
Java SE не имеет этой проблемы, поскольку он вообще не поддерживает AES128/CTR/NoPadding, по крайней мере, с поставщиком по умолчанию. Я только что попробовал, Java 7 Oracle (но, возможно, моя установка запуталась, и я использую OpenJDK, все возможно).   -  person maaartinus    schedule 13.10.2014


Ответы (3)


Я только что протестировал AES в режиме CTR с использованием Oracle Java 1.7 и не могу проверить ваши наблюдения:

Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
KeyGenerator kg = KeyGenerator.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, kg.generateKey());
System.out.println(c.update(new byte[1]).length);  // output: 1
System.out.println(c.update(new byte[20]).length); // output: 20

Возможно, вы используете дефектную стороннюю реализацию, потому что «AES128 / CTR / NoPadding» не является известным шифром в моей системе.

person Robert    schedule 30.04.2013
comment
Вы правы, это проблема Android Dalvik VM, а не Java SE. - person K J; 01.05.2013

У меня была точно такая же проблема сегодня, и я решил ее только сейчас.

Проблема в вашем провайдере, которым, вероятно, является Bouncy Castle. Когда вы вызываете getInstance(), просто укажите имя вашего алгоритма (в моем случае это «AES / CTR / NoPadding»). НЕ указывать провайдера.

Пусть код объяснит сам:

Как сказал @Robert, следующий код работает правильно:

Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
KeyGenerator kg = KeyGenerator.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, kg.generateKey());
System.out.println(c.update(new byte[1]).length);  // output: 1
System.out.println(c.update(new byte[20]).length); // output: 20

Однако, если вместо этого вы укажете поставщика как «BC», это будет неправильно:

Cipher c = Cipher.getInstance("AES/CTR/NoPadding", "BC");
KeyGenerator kg = KeyGenerator.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, kg.generateKey());
System.out.println(c.update(new byte[20]).length); // output: 16
System.out.println(c.update(new byte[1]).length);  // null pointer exception

Это можно рассматривать как ошибку Bouncy Castle или своего рода (странную, но разумную) функцию.

person monnand    schedule 30.04.2013
comment
В моем случае проблема была в Android Dalvik, но спасибо, что указали на то, что у настраиваемого провайдера такая же проблема. - person K J; 01.05.2013
comment
Спасибо, что сообщили мне, что у Android такая же проблема (функция?). Это раздражает, поскольку преимуществом режима счетчика является то, что его можно использовать без заполнения. - person monnand; 07.05.2013
comment
Я знаю, что этот вопрос был некоторое время назад ... Проблема все еще присутствует в современных источниках BC? - person jww; 18.01.2016

Мне не приходилось решать эту проблему самому, но одним из способов решения этой проблемы было бы создание ключевого потока вручную и самостоятельная обработка XORing.

То есть вы должны переключиться с AES/CTR/NoPadding на AES/ECB/NoPadding и многократно зашифровать свой с увеличением значения счетчика, когда вам нужны свежие данные для XOR с зашифрованным текстом.

Далеко не идеально, но, думаю, сработает.

person Duncan Jones    schedule 30.04.2013
comment
Спасибо за предложение. Я думаю, что могу использовать CTR с массивом нулей, чтобы получить ключевой поток. - person K J; 01.05.2013