Почему вывод Blowfish в Java и PHP отличается всего на 2 символа?

У меня есть сценарий шифрования blowfish на PHP и JAVA, который работал нормально до сегодняшнего дня, когда я столкнулся с проблемой.

Один и тот же контент шифруется по-разному в Java и PHP всего двумя символами, что действительно странно.

PHP

wTHzxfxLHdMm/JMFnoh0hciS/JADvFFg

Джава

wTHzxfxLHdMm/JMFnoh0hciS/D8DvFFg
-------------------------^^

Как видите, эти две позиции не совпадают. К сожалению, это реальный адрес электронной почты, и я не могу им поделиться. Также мне не удалось воспроизвести проблему с несколькими другими протестированными значениями. Я пытался изменить классы кодирования Base64 на Java, и это не помогло.

Исходный код для PHP находится здесь, а для Java здесь.

Что я могу сделать, чтобы решить эту проблему?


person Pentium10    schedule 20.07.2011    source источник
comment
Может быть, это связано с кодировкой символов, используемой для представления адреса электронной почты в Java и PHP? Есть ли в адресе символ, отличный от ASCII?   -  person OpenSauce    schedule 20.07.2011
comment
Нет там нет, только альфа и точка.   -  person Pentium10    schedule 20.07.2011
comment
только Blowfish делает это? Как насчет мд5/ша?   -  person Quamis    schedule 20.07.2011
comment
При декодировании base64 разница составляет ровно один байт, 20-й. Я посмотрел на код и сразу не заметил никаких проблем.   -  person President James K. Polk    schedule 20.07.2011
comment
@Quamis Мне нужно шифрование, а не хеширование, так как мне нужно расшифровать значения на другом конце.   -  person Pentium10    schedule 20.07.2011
comment
Предложение Quamis об использовании хеша кажется странным, но попытка применить другой симметричный алгоритм к тому же набору данных кажется хорошим диагностическим подходом.   -  person symcbean    schedule 20.07.2011
comment
GregS говорит, что разница в 20-м байте. Пробовали ли вы другие открытые тексты с таким же значением для 20-го байта (и, возможно, двух окружающих его байтов)? Попробуйте сравнить значения байтов открытых текстов в PHP и Java.   -  person rossum    schedule 20.07.2011
comment
Ваш PHP-код говорит, что открытый текст дополнен NULL, но в Java вы используете PKCS5Padding.   -  person Chochos    schedule 08.08.2011
comment
@Chochos, как это исправить?   -  person Pentium10    schedule 11.08.2011
comment
Я не знаю PHP, чтобы рассказать вам, как это реализовать, но вы можете вручную дополнить данные в Java нулями перед шифрованием.   -  person Chochos    schedule 12.08.2011


Ответы (3)


Давайте посмотрим на ваш код Java:

String c = new String(Test.encrypt((new String("thevalue")).getBytes(),
                                   (new String("mykey")).getBytes()));
...
System.out.println("Base64 encoded String:" +
                   new sun.misc.BASE64Encoder().encode(c.getBytes()));

Что вы делаете здесь:

  1. Преобразуйте строку открытого текста в байты, используя системную кодировку по умолчанию.
  2. преобразовать ключ в байты, используя системную кодировку по умолчанию
  3. зашифровать байты
  4. преобразовать зашифрованные байты обратно в строку, используя системную кодировку по умолчанию.
  5. преобразовать зашифрованную строку обратно в байты, используя системную кодировку по умолчанию.
  6. закодируйте эти зашифрованные байты, используя Base64.

Проблема заключается в шаге 4. Предполагается, что произвольный массив байтов представляет строку в кодировке вашей системы по умолчанию, а обратное кодирование этой строки дает тот же байт []. Это справедливо для некоторых кодировок (например, серии ISO-8859), но не для других. В Java, когда какой-то байт (или последовательность байтов) не представим в данной кодировке, он будет заменен каким-то другим символом, который позже для переконвертирования будет сопоставлен с байтом 63 (ASCII ?). Собственно, в документации даже сказано:

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

В вашем случае нет причин делать это вообще - просто используйте байты, которые ваш метод encrypt выводит напрямую, чтобы преобразовать их в Base64.

byte[] encrypted = Test.encrypt("thevalue".getBytes(),
                                "mykey".getBytes());
System.out.println("Base64 encoded String:"+ new sun.misc.BASE64Encoder().encode(encrypted));

(Также обратите внимание, что я удалил здесь лишние вызовы конструктора new String("..."), хотя это не относится к вашей проблеме.)

Важно помнить: Никогда не преобразовывайте произвольный байт[], полученный не в результате кодирования строки, в строку. Вывод алгоритма шифрования (и большинства других криптографических алгоритмов, кроме дешифрования) безусловно относится к категории данных, которые не следует преобразовывать в строку.

И никогда не используйте системную кодировку по умолчанию, если вам нужны переносимые программы.

person Paŭlo Ebermann    schedule 20.08.2011

Ваш код кажется мне правильным.

Похоже, у вас есть завершающий пробел во входных данных одной из этих программ, и это только один. Я скажу вам, почему:

Каждый из этих 4-символьных блоков представляет 3 символа в зашифрованной строке. Другая часть (JA и D8 в 7-м блоке) на самом деле происходит от одного другого персонажа.

wTHz xfxL HdMm /JMF noh0 hciS /JAD vFFg

wTHz xfxL HdMm /JMF noh0 hciS /D8D vFFg

Если я правильно понял, ваш адрес электронной почты состоит из 19 символов. 20-й символ в одной из ваших входных строк — это пробел.

person n0rm1e    schedule 21.07.2011
comment
Наблюдение, что только один выходной байт неверен, является хорошим (и помогло написать мой ответ), но это не переносится на один неправильный входной байт - Blowfish - это блочный шифр, и он используется в режиме CBC, например. один отличающийся бит на входе получит совершенно другой 64-битный блок (8 байтов) на выходе. (Для режима счетчика ваше наблюдение будет правильным). - person Paŭlo Ebermann; 20.08.2011
comment
Суть CBC в том, что если у вас есть что-то другое, остальная часть шифротекста результата будет другой. Но в том же блоке (скажем, 8 байтов) первые байты не обязательно зависят от того, что более поздние байты в том же блоке отличаются. - person n0rm1e; 21.08.2011
comment
Важно то, что блочный шифр (если он хороший) представляет собой псевдослучайную перестановку... что означает, что изменение одного бита на входе изменяет (в среднем) половину битов на выходе. CBC применяет блочный шифр к открытому тексту (вместе с зашифрованным текстом предыдущего блока), так что мы получим полностью нераспознаваемый блок. Следующие блоки, разумеется, тоже будут уничтожены. Для сравнения: ECB уничтожит только один блок, а режим CTR только поменяет местами этот бит в выводе (поскольку блочный шифр не применяется к открытому тексту). - person Paŭlo Ebermann; 21.08.2011
comment
CFB поменяет местами один бит и уничтожит следующие блоки. OFB здесь как CTR. - person Paŭlo Ebermann; 21.08.2011
comment
Спасибо Павел за объяснение. - person n0rm1e; 21.08.2011

Вопрос: Пробовали ли вы расшифровывать сгенерированный PHP зашифрованный текст с помощью связанной библиотеки расшифровки PHP? Вы пробовали использовать связанную библиотеку расшифровки JAVA для расшифровки зашифрованного текста JAVA?

Если оба выдают разные результаты, то один из них ДОЛЖЕН не расшифровываться.

Это один PHP или Java?

Что бы это ни было, я бы попытался воспроизвести еще один подобный сбой с общедоступной строкой... передать эту строку в качестве модульного теста разработчику или разработчикам, создавшим код шифрования/дешифрования на языке, Поездка зашифровать/расшифровать не удается.

Тогда... подождите, пока они это исправят.

Не уверен в каких-либо более быстрых решениях - за исключением, возможно, смены поставщиков библиотек шифрования/дешифрования... или создания собственного...

person Peter Sherman    schedule 12.08.2011
comment
Я пробовал их, и они работают нормально. Эта проблема возникает только 1 раз для 1000 значений и случайным образом, но постоянно для этих значений. - person Pentium10; 12.08.2011