Проблемы преобразования массива байтов в строку и обратно в байтовый массив
В этой теме много вопросов, это же решение, но это не работает для меня. У меня простой тест с шифрованием. Само шифрование/дешифрование работает (до тех пор, пока я обрабатываю этот тест с помощью массива байтов, а не как строки). Проблема в том, что вы не хотите обрабатывать его как массив байтов, а как String, но когда я кодирую массив байтов в строку и обратно, полученный массив байтов отличается от исходного массива байтов, поэтому дешифрование больше не работает. Я пробовал следующие параметры в соответствующих строковых методах: UTF-8, UTF8, UTF-16, UTF8. Ни один из них не работает. Результирующая байтная матрица отличается от оригинала. Любые идеи, почему это так?
Encrypter:
public class NewEncrypter
{
private String algorithm = "DESede";
private Key key = null;
private Cipher cipher = null;
public NewEncrypter() throws NoSuchAlgorithmException, NoSuchPaddingException
{
key = KeyGenerator.getInstance(algorithm).generateKey();
cipher = Cipher.getInstance(algorithm);
}
public byte[] encrypt(String input) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] inputBytes = input.getBytes("UTF-16");
return cipher.doFinal(inputBytes);
}
public String decrypt(byte[] encryptionBytes) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] recoveredBytes = cipher.doFinal(encryptionBytes);
String recovered = new String(recoveredBytes, "UTF-16");
return recovered;
}
}
Это тест, где я его пробую:
public class NewEncrypterTest
{
@Test
public void canEncryptAndDecrypt() throws Exception
{
String toEncrypt = "FOOBAR";
NewEncrypter encrypter = new NewEncrypter();
byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
System.out.println("encryptedByteArray:" + encryptedByteArray);
String decoded = new String(encryptedByteArray, "UTF-16");
System.out.println("decoded:" + decoded);
byte[] encoded = decoded.getBytes("UTF-16");
System.out.println("encoded:" + encoded);
String decryptedText = encrypter.decrypt(encoded); //Exception here
System.out.println("decryptedText:" + decryptedText);
assertEquals(toEncrypt, decryptedText);
}
}
Ответы
Ответ 1
Не рекомендуется хранить зашифрованные данные в Строках, потому что они предназначены для текста, читаемого человеком, а не для произвольных двоичных данных. Для двоичных данных лучше всего использовать byte[]
.
Однако, если вы должны сделать это, вы должны использовать кодировку, которая имеет сопоставление 1-к-1 между байтами и символами, то есть, где каждая последовательность байтов может быть отображена в уникальную последовательность символов и обратно. Одна такая кодировка ISO-8859-1, то есть:
String decoded = new String(encryptedByteArray, "ISO-8859-1");
System.out.println("decoded:" + decoded);
byte[] encoded = decoded.getBytes("ISO-8859-1");
System.out.println("encoded:" + java.util.Arrays.toString(encoded));
String decryptedText = encrypter.decrypt(encoded);
Другие распространенные кодировки, которые не теряют данные, шестнадцатеричные и base64, но, к сожалению, вам нужна вспомогательная библиотека. Стандартный API не определяет для них классы.
С UTF-16 программа потерпит неудачу по двум причинам:
- String.getBytes( "UTF-16" ) добавляет символ байта-маркера к выходу для определения порядка байтов. Вы должны использовать UTF-16LE или UTF-16BE, чтобы этого не произошло.
- Не все последовательности байтов могут быть сопоставлены с символами в UTF-16. Во-первых, текст, закодированный в UTF-16, должен иметь четное количество байтов. Во-вторых, UTF-16 имеет механизм кодирования символов Юникода за пределами U + FFFF. Это означает, что, например, существуют последовательности из 4 байтов, которые отображаются только для одного символа Юникода. Чтобы это было возможно, первые 2 байта из 4 не кодируют какой-либо символ в UTF-16.
Ответ 2
Принятое решение не будет работать, если ваш String
имеет некоторые нестандартные символы, такие как š, ž, ć, Ō, ō, Ū
и т.д.
Следующий код работал хорошо для меня.
byte[] myBytes = Something.getMyBytes();
String encodedString = Base64.encodeToString(bytes, Base64.NO_WRAP);
byte[] decodedBytes = Base64.decode(encodedString, Base64.NO_WRAP);
Ответ 3
Теперь я нашел еще одно решение...
public class NewEncrypterTest
{
@Test
public void canEncryptAndDecrypt() throws Exception
{
String toEncrypt = "FOOBAR";
NewEncrypter encrypter = new NewEncrypter();
byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
String encoded = String.valueOf(Hex.encodeHex(encryptedByteArray));
byte[] byteArrayToDecrypt = Hex.decodeHex(encoded.toCharArray());
String decryptedText = encrypter.decrypt(byteArrayToDecrypt);
System.out.println("decryptedText:" + decryptedText);
assertEquals(toEncrypt, decryptedText);
}
}
Ответ 4
Ваша проблема в том, что вы не можете построить строку UTF-16 (или любую другую кодировку) из произвольного байтового массива (см. UTF-16 в Википедии). Однако вам решать сериализовать и десериализовать зашифрованный байтовый массив без каких-либо потерь, чтобы, скажем, сохранить его и использовать позже. Здесь измененный код клиента, который должен дать вам представление о том, что на самом деле происходит с массивами байтов:
public static void main(String[] args) throws Exception {
String toEncrypt = "FOOBAR";
NewEncrypter encrypter = new NewEncrypter();
byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
System.out.println("encryptedByteArray:" + Arrays.toString(encryptedByteArray));
String decoded = new String(encryptedByteArray, "UTF-16");
System.out.println("decoded:" + decoded);
byte[] encoded = decoded.getBytes("UTF-16");
System.out.println("encoded:" + Arrays.toString(encoded));
String decryptedText = encrypter.decrypt(encryptedByteArray); // NOT the "encoded" value!
System.out.println("decryptedText:" + decryptedText);
}
Это вывод:
encryptedByteArray:[90, -40, -39, -56, -90, 51, 96, 95, -65, -54, -61, 51, 6, 15, -114, 88]
decoded:<some garbage>
encoded:[-2, -1, 90, -40, -1, -3, 96, 95, -65, -54, -61, 51, 6, 15, -114, 88]
decryptedText:FOOBAR
decryptedText
верен, если он восстановлен из оригинала encryptedByteArray
. Обратите внимание, что значение encoded
не совпадает с encryptedByteArray
из-за потери данных во время преобразования byte[] -> String("UTF-16")->byte[]
.