web-dev-qa-db-fra.com

Problèmes lors de la conversion d'un tableau d'octets en chaîne et retour en tableau d'octets

Il y a beaucoup de questions sur ce sujet, la même solution, mais cela ne fonctionne pas pour moi. J'ai un test simple avec un cryptage. Le chiffrement/déchiffrement lui-même fonctionne (tant que je gère ce test avec le tableau d'octets lui-même et non en tant que chaînes). Le problème est que vous ne voulez pas le gérer sous forme de tableau d'octets mais sous forme de chaîne, mais lorsque je code le tableau d'octets en chaîne, le tableau d'octets résultant diffère du tableau d'octets d'origine. J'ai essayé les paramètres suivants dans les méthodes de chaîne correspondantes: UTF-8, UTF8, UTF-16, UTF8. Aucun d'entre eux ne fonctionne. Le tableau d'octets résultant diffère de l'original. Des idées pourquoi c'est ainsi?

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;
    }
}

C'est le test où je l'essaie:

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);
    }
}
39
Bevor

Ce n'est pas une bonne idée de stocker des données cryptées dans des chaînes, car elles sont pour le texte lisible par l'homme, pas pour les données binaires arbitraires. Pour les données binaires, il vaut mieux utiliser byte[].

Cependant, si vous le faites devez, vous devez utiliser un codage avec un mappage 1 à 1 entre octets et caractères, c’est-à-dire où chaque séquence d’octets peut être mappée à une séquence unique. de personnages et retour. Un tel encodage est ISO-8859-1, c'est-à-dire:

    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);

Les autres encodages courants qui ne perdent pas de données sont hexadécimal _ et base64, mais vous avez malheureusement besoin d'une bibliothèque d'assistance. L'API standard ne définit pas de classes pour elles.

Avec UTF-16, le programme échouerait pour deux raisons:

  1. String.getBytes ("UTF-16") ajoute un caractère de marqueur d'octet à la sortie afin d'identifier l'ordre des octets. Vous devez utiliser UTF-16LE ou UTF-16BE pour que cela ne se produise pas.
  2. Toutes les séquences d'octets ne peuvent pas être mappées sur des caractères UTF-16. Tout d'abord, le texte encodé en UTF-16 doit avoir un nombre d'octets pair. Deuxièmement, UTF-16 a un mécanisme pour coder des caractères unicode au-delà de U + FFFF. Cela signifie que par exemple il existe des séquences de 4 octets qui mappent sur un seul caractère unicode. Pour que cela soit possible, les 2 premiers octets sur 4 ne codent aucun caractère en UTF-16.
89
Joni

La solution acceptée ne fonctionnera pas si votre String a des caractères inhabituels tels que š, ž, ć, Ō, ō, Ū, etc.

Suivre le code a bien fonctionné pour moi.

byte[] myBytes = Something.getMyBytes();
String encodedString = Base64.encodeToString(bytes, Base64.NO_WRAP);
byte[] decodedBytes = Base64.decode(encodedString, Base64.NO_WRAP);
16
Aleksandar Ilic

Maintenant, j'ai trouvé une autre solution aussi ...

    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);
        }
    }
5
Bevor

Votre problème est que vous ne pouvez pas construire de chaîne UTF-16 (ou autre) à partir d'un tableau d'octets arbitraire (voir UTF-16 sur Wikipedia ). Cependant, il vous appartient de sérialiser et de désérialiser le tableau d'octets chiffré sans aucune perte, afin, par exemple, de le conserver et de l'utiliser ultérieurement. Voici le code client modifié qui devrait vous donner un aperçu de ce qui se passe réellement avec les tableaux d'octets:

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);
}

C'est la sortie:

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

La decryptedText est correcte lorsqu'elle est restaurée à partir de la encryptedByteArray d'origine. Veuillez noter que la valeur encoded n'est pas identique à encryptedByteArray, en raison de la perte de données lors de la conversion byte[] -> String("UTF-16")->byte[].

0
Alexander Pavlov