Хеширование необработанных байтов в Python и Java дает разные результаты.

Я пытаюсь воспроизвести поведение функции Python 2.7 в Java, но получаю разные результаты при выполнении (на первый взгляд) идентичной последовательности байтов через хэш SHA-256. Байты генерируются путем манипулирования очень большим целым числом (длиной ровно 2048 бит) определенным образом (2-я строка моего примера кода Python).

В моих примерах исходное 2048-битное целое число хранится как big_int и bigInt в Python и Java соответственно, и обе переменные содержат одно и то же число.

Код Python2, который я пытаюсь воспроизвести:

raw_big_int = ("%x" % big_int).decode("hex")

buff = struct.pack(">i", len(raw_big_int) + 1) + "\x00" + raw_big_int

pprint("Buffer contains: " + buff)
pprint("Encoded: " + buff.encode("hex").upper())

digest = hashlib.sha256(buff).digest()

pprint("Digest contains: " + digest)
pprint("Encoded: " + digest.encode("hex").upper())

Запуск этого кода выводит следующее (обратите внимание, что единственный результат, который меня действительно интересует, это последний результат — дайджест в шестнадцатеричном коде. Остальные 3 вывода предназначены только для того, чтобы посмотреть, что происходит внутри ):

'Buffer contains: \x00\x00\x01\x01\x00\xe3\xbb\xd3\x84\x94P\xff\x9c\'\xd0P\xf2\xf0s,a^\xf0i\xac~\xeb\xb9_\xb0m\xa2&f\x8d~W\xa0\xb3\xcd\xf9\xf0\xa8\xa2\x8f\x85\x02\xd4&\x7f\xfc\xe8\xd0\xf2\xe2y"\xd0\x84ck\xc2\x18\xad\xf6\x81\xb1\xb0q\x19\xabd\x1b>\xc8$g\xd7\xd2g\xe01\xd4r\xa3\x86"+N\\\x8c\n\xb7q\x1c \x0c\xa8\xbcW\x9bt\xb0\xae\xff\xc3\x8aG\x80\xb6\x9a}\xd9*\x9f\x10\x14\x14\xcc\xc0\xb6\xa9\x18*\x01/eC\x0eQ\x1b]\n\xc2\x1f\x9e\xb6\x8d\xbfb\xc7\xce\x0c\xa1\xa3\x82\x98H\x85\xa1\\\xb2\xf1\'\xafmX|\x82\xe7%\x8f\x0eT\xaa\xe4\x04*\x91\xd9\xf4e\xf7\x8c\xd6\xe5\x84\xa8\x01*\x86\x1cx\x8c\xf0d\x9cOs\xebh\xbc1\xd6\'\xb1\xb0\xcfy\xd7(\x8b\xeaIf6\xb4\xb7p\xcdgc\xca\xbb\x94\x01\xb5&\xd7M\xf9\x9co\xf3\x10\x87U\xc3jB3?vv\xc4JY\xc9>\xa3cec\x01\x86\xe9c\x81F-\x1d\x0f\xdd\xbf\xe8\xe9k\xbd\xe7c5'
'Encoded: 0000010100E3BBD3849450FF9C27D050F2F0732C615EF069AC7EEBB95FB06DA226668D7E57A0B3CDF9F0A8A28F8502D4267FFCE8D0F2E27922D084636BC218ADF681B1B07119AB641B3EC82467D7D267E031D472A386222B4E5C8C0AB7711C200CA8BC579B74B0AEFFC38A4780B69A7DD92A9F101414CCC0B6A9182A012F65430E511B5D0AC21F9EB68DBF62C7CE0CA1A382984885A15CB2F127AF6D587C82E7258F0E54AAE4042A91D9F465F78CD6E584A8012A861C788CF0649C4F73EB68BC31D627B1B0CF79D7288BEA496636B4B770CD6763CABB9401B526D74DF99C6FF3108755C36A42333F7676C44A59C93EA36365630186E96381462D1D0FDDBFE8E96BBDE76335'
'Digest contains: Q\xf9\xb9\xaf\xe1\xbey\xdc\xfa\xc4.\xa9 \xfckz\xfeB\xa0>\xb3\xd6\xd0*S\xff\xe1\xe5*\xf0\xa3i'
'Encoded: 51F9B9AFE1BE79DCFAC42EA920FC6B7AFE42A03EB3D6D02A53FFE1E52AF0A369'

Ниже приведен мой код Java. Когда я тестирую его, я получаю то же значение для входного буфера, но другое значение для дайджеста. (bigInt содержит объект BigInteger, содержащий тот же номер, что и big_int в приведенном выше примере Python)

byte[] rawBigInt = bigInt.toByteArray();

ByteBuffer buff = ByteBuffer.allocate(rawBigInt.length + 4);
buff.order(ByteOrder.BIG_ENDIAN);
buff.putInt(rawBigInt.length).put(rawBigInt);

System.out.print("Buffer contains: ");
System.out.println( DatatypeConverter.printHexBinary(buff.array()) );


MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(buff);
byte[] digest = hash.digest();

System.out.print("Digest contains: ");
System.out.println( DatatypeConverter.printHexBinary(digest) );

Обратите внимание, что в моем примере с Python я начал буфер с len(raw_big_int) + 1 упакованного, тогда как в Java я начал только с rawBigInt.length. Я также опустил лишний 0-байт ("\x00") при написании на Java. Я сделал и то, и другое по одной и той же причине: в моих тестах вызов toByteArray() для BigInteger возвращал массив byte, уже начинающийся с 0-байта, который был ровно на 1 байт длиннее, чем последовательность байтов Python. Итак, по крайней мере, в моих тестах len(raw_big_int) + 1 равнялось rawBigInt.length, поскольку rawBigInt начиналось с 0-байта, а raw_big_int — нет.

Хорошо, кроме этого, вот вывод кода Java:

Buffer contains: 0000010100E3BBD3849450FF9C27D050F2F0732C615EF069AC7EEBB95FB06DA226668D7E57A0B3CDF9F0A8A28F8502D4267FFCE8D0F2E27922D084636BC218ADF681B1B07119AB641B3EC82467D7D267E031D472A386222B4E5C8C0AB7711C200CA8BC579B74B0AEFFC38A4780B69A7DD92A9F101414CCC0B6A9182A012F65430E511B5D0AC21F9EB68DBF62C7CE0CA1A382984885A15CB2F127AF6D587C82E7258F0E54AAE4042A91D9F465F78CD6E584A8012A861C788CF0649C4F73EB68BC31D627B1B0CF79D7288BEA496636B4B770CD6763CABB9401B526D74DF99C6FF3108755C36A42333F7676C44A59C93EA36365630186E96381462D1D0FDDBFE8E96BBDE76335
Digest contains: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855

Как видите, содержимое буфера выглядит одинаково как в Python, так и в Java, но дайджесты явно различаются. Может ли кто-нибудь указать, где я ошибаюсь?

Я подозреваю, что это как-то связано со странным способом, которым Python хранит байты — переменные raw_big_int и buff отображаются как тип str в интерпретаторе, а при выводе сами по себе имеют странный формат с '\x's, который равен в некоторых местах почти так же, как и сами байты, но в других - полная тарабарщина. У меня недостаточно опыта работы с Python, чтобы точно понять, что здесь происходит, и мои поиски оказались безрезультатными.

Кроме того, поскольку я пытаюсь перенести код Python на Java, я не могу просто изменить Python — моя цель — написать код Java, который принимает те же входные данные и производит такие же выходные данные. Я искал (этот вопрос в частности, казалось связанным), но не нашел ничего, что могло бы мне помочь. Заранее спасибо, хотя бы за то, что прочитали этот многословный вопрос! :)


person jming    schedule 25.07.2016    source источник
comment
Кто минусовал этот вопрос? Честно говоря, я понятия не имею, какие вопросы можно было бы задать вам, если бы не этот. Мой разум затуманен.   -  person John Kugelman    schedule 25.07.2016


Ответы (1)


В Java у вас есть данные в буфере, но все позиции курсора неверны. После того, как вы записали свои данные в ByteBuffer, это выглядит так, где x представляют ваши данные, а 0 — это незаписанные байты в буфере:

xxxxxxxxxxxxxxxxxxxx00000000000000000000000000000000000000000
                    ^ position                               ^ limit

Курсор располагается после данных, которые вы записали. Чтение в этот момент будет читать от position до limit, то есть байтов, которые вы не записали.

Вместо этого вы хотите это:

xxxxxxxxxxxxxxxxxxxx00000000000000000000000000000000000000000
^ position          ^ limit

где позиция равна 0, а ограничение — это количество записанных вами байтов. Чтобы туда попасть, позвоните flip(). Переключение буфера концептуально переключает его из режима записи в режим чтения. Я говорю «концептуально», потому что у ByteBuffers нет явных режимов чтения и записи, но вы должны думать о них так, как будто они есть.

(Противоположная операция: compact(), который возвращается в режим чтения.)

person John Kugelman    schedule 25.07.2016
comment
вам также нужно упомянуть, что buff.array() - это весь буферный массив, а не только то, что было написано, что может быть разными вещами. - person ; 25.07.2016
comment
В этом случае я выделил ByteBuffer точно с размером данных, которые я в него поместил, поэтому резервный массив оказался того же размера - до того, как я не совсем понял, как буфер обрабатывает свой резервный массив, но это делает теперь гораздо больше смысла. Спасибо! - person jming; 25.07.2016