Java byterray to string должен быть равен строке python bytearray при создании секрета с помощью SecretKeyFactory

У меня есть задача переписать некоторый криптокод Python на java. Я новичок в питоне. Код Python:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
backend = default_backend()  



PASSWORD = bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))

key = PBKDF2HMAC(hashes.SHA256(), 32, salt, iterations, backend).derive(PASSWORD)

Моя реализация Java:

import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.PBEKeySpec;
     byte[] PASSWORD = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey tmp = factory.generateSecret(new PBEKeySpec(new String(PASSWORD).toCharArray(), salt, iterations, 256));
    byte[] key = tmp.getEncoded();

Как видите, PASSWORD — это массив байтов, который я получаю из шестнадцатеричной строки, т. е. 010203….0F10, я не могу его изменить (т. е. не могу указать его как строку в реализации Python, поскольку я понимаю серверные преобразования ПАРОЛЬ к байтовому массиву также). Все работало нормально с этим фиктивным ПАРОЛЕМ, т.е. ключи, сгенерированные кодом python и java, были равны. Но я столкнулся с проблемой, когда пароль изменился на произвольный, т.е. например, AFFFFFFFFDBGEHTH.... Насколько я понимаю проблема с представлением массива байтов java в виде целых чисел со знаком. т.е. когда я конвертирую шестнадцатеричный FFFAAABBBCCCDDDDFFAAAAAAAAAAAABB, например, в байтовый массив, это будет байтовый массив [-1, -6, -86, -69, -68, -52, -35, -35, -1, -86, -86, - 86, -86, -86, -86, -69], но в питоне будет [255, 250, 170, 187, 188, 204, 221, 221, 255, 170, 170, 170, 170, 170, 170 , 187]. Затем, когда я конвертирую массив байтов java в charArray для конструктора PBEKeySpec - new PBEKeySpec (новая строка (новый байт [] {-1, -6, -86, -69, -68, -52, -35, -35, -1 , -86, -86, -86, -86, -86, -86, -69}).toCharArray()... работает неожиданно.

Как мне изменить свой java-код, чтобы получить тот же ключ, что и в python? Насколько я понимаю, мне нужно закодировать строку массива байтов java в то же значение, что и в методе python .derive(...). Заранее спасибо.

ОБНОВИТЬ:

salt       = b'salt'
PASSWORD = = bytes((255, 250, 170, 187, 188, 204, 221, 221, 255, 170, 170, 170, 170, 170, 170, 187))
key = PBKDF2HMAC(hashes.SHA256(), 32, salt, 512, backend).derive(PASSWORD)

а также

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
password = new String(new byte[]{-1, -6, -86, -69, -68, -52, -35, -35, -1, -86, -86, -86, -86, -86, -86, -69});
var key = secretKeyFactory
                    .generateSecret(new PBEKeySpec(password.toCharArray(), 
"salt".getBytes(), 512, 256))
                    .getEncoded();

должен дать тот же результат. Он работает для нового пароля byte[]{1,2,3,4,....16}.

ОБНОВЛЕНИЕ 2: я изменил пароль на unsigned int[], но он все равно не работает:

    char[] password = new char[PASSWORD.length];
            for (int i = 0; i<PASSWORD.length; password[i] = (char)(PASSWORD[i++] & 0xFF));
    var key = secretKeyFactory
                    .generateSecret(new PBEKeySpec(password, "salt".getBytes(), 512, 256))
                    .getEncoded();
    

person Valeriy K.    schedule 15.09.2020    source источник
comment
-1 и 255 — одно и то же значение байта 011111111b. Первая интерпретация со знаком (дополнение до 2), вторая интерпретация без знака. Все зависит от того, как вы (обычно человек) хотите это интерпретировать. Компьютер не запутается.   -  person Kayaman    schedule 15.09.2020
comment
Да, я понимаю. Но как я вижу из эксперимента реализации java и python разные. Когда я начинаю работать с байтами больше 127, я получаю неожиданные результаты.   -  person Valeriy K.    schedule 15.09.2020
comment
Затем покажите код, в котором вы получаете неожиданные результаты.   -  person Kayaman    schedule 15.09.2020
comment
Не печатать массив байтов как значения. Вместо этого используйте стандартизированный метод, такой как шестнадцатеричное кодирование или кодирование base64, тогда вы можете сравнить их даже между разными языками программирования/средами выполнения.   -  person Robert    schedule 15.09.2020


Ответы (2)


Помимо различных дайджестов (см. 1-й ответ), проблема в том, что ключ, полученный с помощью PBKDF2WithHmacSHA256, является экземпляром PBKDF2KeyImpl, который требует строку в качестве пароля. Эта строка имеет кодировку UTF8 в PBKDF2KeyImpl (см. документацию по классу PBKDF2KeyImpl). Однако здесь пароль представляет собой (произвольную) последовательность байтов, которая, как правило, несовместима с UTF8, поэтому данные при декодировании UTF8 повреждаются. Возможное решение — заменить PBEKeySpec на PKCS5S2ParametersGenerator BouncyCastle, который ожидает пароль в виде массива байтов (в init):

import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
...
byte[] salt = "salt".getBytes(StandardCharsets.UTF_8);
int iterations = 512;
byte[] PASSWORD = new byte[] { (byte)255, (byte)250, (byte)170, (byte)187, (byte)188, (byte)204, (byte)221, (byte)221, (byte)255, (byte)170, (byte)170, (byte)170, (byte)170, (byte)170, (byte)170, (byte)187 };
PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PASSWORD, salt, iterations);
byte[] keyBytes = ((KeyParameter)generator.generateDerivedParameters(256)).getKey(); 
// with bytesToHex from https://stackoverflow.com/a/9855338
System.out.println(bytesToHex(keyBytes).toLowerCase());  // d8aa4772e9648572611fe6dca7f653353de934cdb3b29fab94eb13ba2b198b9f

Теперь результат соответствует коду Python:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

salt = b'salt'
iterations = 512
PASSWORD = bytes((255, 250, 170, 187, 188, 204, 221, 221, 255, 170, 170, 170, 170, 170, 170, 187))
key = PBKDF2HMAC(hashes.SHA256(), 32, salt, iterations).derive(PASSWORD)

print(key.hex()) # d8aa4772e9648572611fe6dca7f653353de934cdb3b29fab94eb13ba2b198b9f
person user 9014097    schedule 15.09.2020
comment
Спасибо большое ????????????! Оно работает. Я перешел на SHA256Digest() для своей ситуации. - person Valeriy K.; 15.09.2020
comment
@ValeriyK.- Пожалуйста. Да, SHA256 более последователен, я обновил его соответствующим образом. - person user 9014097; 15.09.2020

Вы используете SHA1 в коде Java и SHA256 в коде Python, поэтому вы получаете разные результаты. Используйте PBKDF2WithHmacSHA256.

person Paul Kehrer    schedule 15.09.2020
comment
Спасибо. Извините, я добавил старый код. Теперь я использую PBKDF2WithHmacSHA256, и он все еще не работает. Я исправил. - person Valeriy K.; 15.09.2020