2012-02-01 10 views
30

Istnieje wiele pytań na ten temat, to samo rozwiązanie, ale to nie działa dla mnie. Mam prosty test z szyfrowaniem. Sam szyfrowanie/deszyfrowanie działa (tak długo jak obsługuję ten test z samą tablicą bajtów, a nie jako Strings). Problem polega na tym, że nie chcemy traktować go jako tablicy bajtów, ale jako String, ale kiedy koduję tablicę bajtów do łańcucha i powrotem, wynikowa tablica bajtów różni się od oryginalnej tablicy bajtów, więc odszyfrowywanie już nie działa. Próbowałem następujące parametry w odpowiednich metod ciąg znaków: UTF-8, UTF8, UTF-16, UTF8. Żadna z nich nie działa. Wynikowa tablica bajtów różni się od oryginału. Jakieś pomysły, dlaczego tak się dzieje?Problemy z konwertowaniem tablicy bajtów na ciąg znaków i powrotem do tablicy bajtów

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

To jest test, gdzie próbuję go:

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

Najpierw należy przekonwertować bajty na coś, co można przedstawić jako ciąg. Zwykle poprzez konwersję na hex lub base64. –

+0

Jaka jest rzeczywista różnica w tablicach bajtów przed i po konwersji na ciąg znaków? – Herms

+0

@Roger Lindsjö: dzięki za przywiezienie. Spróbuję natychmiast. – Bevor

Odpowiedz

73

Nie jest dobrym pomysłem przechowywanie zaszyfrowanych danych w łańcuchach znaków, ponieważ są one przeznaczone do odczytu w postaci czytelnej dla człowieka, a nie do arbitralnych danych binarnych. W przypadku danych binarnych najlepiej jest użyć byte[].

Jeśli jednak musi to zrobić należy użyć kodowania, który ma mapowanie1-do-1 między bajtów i znaków, to znaczy, gdzie każda sekwencja bajtów może być odwzorowany na unikalny ciąg znaków , i z powrotem. Jednym z takich jest kodowania ISO-8859-1, to jest:

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

Inne częste kodowania, które nie tracą dane są szesnastkowy i base64, ale niestety trzeba bibliotekę dla nich pomocnika. Standardowy interfejs API nie definiuje dla nich klas.

Z UTF-16 program nie powiedzie się z dwóch powodów:

  1. String.getBytes ("UTF-16") dodaje charakteru bajt-order-marker do wyjścia do określenia kolejności bajtów . Powinieneś użyć UTF-16LE lub UTF-16BE, aby tego nie było.
  2. Nie wszystkie sekwencje bajtów można odwzorować na znaki w UTF-16. Po pierwsze, tekst zakodowany w UTF-16 musi mieć parzystą liczbę bajtów. Po drugie, UTF-16 ma mechanizm kodowania znaków unicode poza U + FFFF. Oznacza to, że np. istnieją sekwencje 4 bajtów, które odwzorowują tylko jeden znak Unicode. Aby było to możliwe pierwsze 2 bajty 4 nie kodują żadnego znaku w UTF-16.
+0

Dzisiaj zobaczyłem, że mam problem podczas używania szyfrowania i deszyfrowania w różnych maszynach wirtualnych. Oczywiście tylko twoje rozwiązanie działa. – Bevor

+0

Twoje podejście do używania Apache Commons Codec powinno również działać, ale musisz rozpowszechniać bibliotekę commons-codec z twoją aplikacją. – Joni

+0

Bardzo pomocna, bardzo dziękuję :) – Spl2nky

0

Twój problem jest, że nie można budować UTF-16 (lub innego kodowania) String z dowolną tablica bajtów (patrz UTF-16 on Wikipedia). Od ciebie zależy jednak na serializacji i deserializacji zaszyfrowanej tablicy bajtów bez żadnych strat, aby, na przykład, utrzymać ją i wykorzystać później. Oto zmodyfikowany kod klienta, który powinien dać pewne wyobrażenie o tym, co rzeczywiście dzieje się z tablicami bajtów:

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

To jest wyjście:

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 jest prawidłowa, gdy przywrócony z oryginalnej encryptedByteArray . Należy pamiętać, że wartość encoded nie jest taka sama jak encryptedByteArray, ze względu na utratę danych podczas konwersji byte[] -> String("UTF-16")->byte[].

+0

Dzięki, w tej chwili znalazłem też inne rozwiązanie (zobacz moją odpowiedź). Niemniej jednak przyjmuję twoją odpowiedź, ponieważ twoje rozwiązanie też działa. – Bevor

+0

Dzisiaj zobaczyłem, że odszyfrowujesz oryginalną tablicę bajtów. Nie jest to w rzeczywistości to, czego chciałem (i niestety miałem problem z odszyfrowaniem go podczas używania go w różnych maszynach wirtualnych), więc działa tylko rozwiązanie Joni. – Bevor

+0

Mój kod był tylko ilustracją błędu, który pojawił się w twoim kodzie, a nie rozwiązaniu serializacyjnym ("Od ciebie zależy jednak na serializacji i deserializacji zaszyfrowanej tablicy bajtów bez żadnej straty"), ponieważ możesz chcieć użyć ton różnych technik serializacji (przy użyciu DataIn/OutputStreams itp.) –

5

Teraz znalazłem inne rozwiązanie też ...

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

Zaakceptowany rozwiązanie nie będzie działać, jeśli String ma kilka nietypowych charcaters takich jak š, ž, ć, Ō, ō, Ū itp

następujący kod pracował ładnie dla mnie.

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

Jedyne działające rozwiązanie, choć musisz polegać na ApacheCommons – AntJavaDev

+3

Używałem 'android.util.Base64'. Jeśli nie chcesz dołączać 'ApacheCommons', domyślam się, że zawsze możesz skopiować plik w swoim projekcie. Oto źródło: https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Base64.java –

+1

Tak, wskazałem, że dla aplikacji java ta klasa narzędziowa ma także JDK 1.8 , ale w poprzednich wersjach java musisz polegać na ApacheCommons, ponieważ jest to jedyna działająca metoda. – AntJavaDev

Powiązane problemy