Проблемы с преобразованием байтового массива в строку и обратно в байтовый массив - PullRequest
45 голосов
/ 01 февраля 2012

Есть много вопросов по этой теме, одно и то же решение, но это не работает для меня. У меня есть простой тест с шифрованием. Само шифрование / дешифрование работает (до тех пор, пока я выполняю этот тест с самим байтовым массивом, а не со строками). Проблема в том, что я не хочу обрабатывать его как байтовый массив, а как 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);
    }
}

Ответы [ 4 ]

95 голосов
/ 01 февраля 2012

Не рекомендуется хранить зашифрованные данные в строках, поскольку они предназначены для читабельного текста, а не для произвольных двоичных данных.Для двоичных данных лучше использовать 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 программа завершится с ошибкой по двум причинам:

  1. String.getBytes ("UTF-16") добавляетсимвол байта-порядка-маркера на выходе для определения порядка байтов.Вы должны использовать UTF-16LE или UTF-16BE, чтобы этого не произошло.
  2. Не все последовательности байтов могут быть сопоставлены с символами в UTF-16.Во-первых, текст, закодированный в UTF-16, должен иметь четное количество байтов.Во-вторых, UTF-16 имеет механизм для кодирования символов Юникода за пределами U + FFFF.Это означает, что, например, существуют последовательности из 4 байтов, которые отображаются только на один символ Юникода.Чтобы это было возможно, первые 2 байта из 4 не кодируют ни один символ в UTF-16.
20 голосов
/ 15 октября 2015

Принятое решение не будет работать, если в вашем String есть несколько нестандартных символов, таких как š, ž, ć, Ō, ō, Ū и т. Д.

Следующий код отлично сработал для меня.

byte[] myBytes = Something.getMyBytes();
String encodedString = Base64.encodeToString(bytes, Base64.NO_WRAP);
byte[] decodedBytes = Base64.decode(encodedString, Base64.NO_WRAP);
5 голосов
/ 01 февраля 2012

Теперь я тоже нашел другое решение ...

    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);
        }
    }
0 голосов
/ 01 февраля 2012

Ваша проблема в том, что вы не можете построить строку 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[].

...