2012-02-01 32 views
30

Hay muchas preguntas con respecto a este tema, la misma solución, pero esto no me funciona. Tengo una prueba simple con una encriptación. El cifrado/descifrado en sí mismo funciona (siempre que maneje esta prueba con la matriz de bytes en sí y no como cadenas). El problema es que no quiero manejarlo como una matriz de bytes sino como una cadena, pero cuando codigo la matriz de bytes a la cadena y viceversa, la matriz de bytes resultante difiere de la matriz de bytes original, por lo que el descifrado ya no funciona. Probé los siguientes parámetros en los métodos de cadena correspondientes: UTF-8, UTF8, UTF-16, UTF8. Ninguno de ellos funciona. El conjunto de bytes resultante difiere del original. ¿Alguna idea de por qué esto es así?Problemas al convertir una matriz de bytes a una cadena y volver a la matriz de bytes

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

Ésta es la prueba en la que lo intento:

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

Primero necesita convertir los bytes a algo que se pueda presentar como una cadena. Por lo general convirtiendo a hexadecimal o base64. –

+0

¿Cuál es la diferencia real que ve en las matrices de bytes antes y después de convertir a una cadena? – Herms

+0

@Roger Lindsjö: gracias por el tipp. Lo intentaré de inmediato. – Bevor

Respuesta

73

No es una buena idea almacenar datos cifrados en cadenas porque son para texto legible por humanos, no para datos binarios arbitrarios. Para datos binarios, es mejor usar byte[].

Sin embargo, si debe hacerlo se debe utilizar una codificación que tiene un mapeo 1-a-1 entre bytes y caracteres, es decir, donde cada secuencia de bytes se puede asignar a una secuencia única de caracteres y de vuelta Uno de estos es la codificación ISO-8859-1 , es decir:

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

Otras codificaciones comunes que no pierden datos son hexadecimal y base 64, pero lamentablemente se necesita una biblioteca de ayuda para ellos. La API estándar no define clases para ellos.

Con UTF-16 el programa sería un fracaso por dos razones:

  1. String.getBytes ("UTF-16") añade un carácter de orden de bytes marcador a la salida para identificar el orden de los bytes . Debe usar UTF-16LE o UTF-16BE para que esto no suceda.
  2. No todas las secuencias de bytes se pueden asignar a caracteres en UTF-16. Primero, el texto codificado en UTF-16 debe tener un número par de bytes. En segundo lugar, UTF-16 tiene un mecanismo para codificar caracteres Unicode más allá de U + FFFF. Esto significa que, p. hay secuencias de 4 bytes que se asignan a un solo carácter Unicode. Para que esto sea posible, los primeros 2 bytes de los 4 no codifican ningún carácter en UTF-16.
+0

Hoy vi que tengo un problema al usar el cifrado y el descifrado en diferentes máquinas virtuales. Obviamente, solo tu solución funciona. – Bevor

+0

Su enfoque de usar Apache Commons Codec también debería funcionar, pero debe distribuir la biblioteca commons-codec con su aplicación. – Joni

+0

Muy útil, muchas gracias :) – Spl2nky

0

Su problema es que no se puede construir una cadena UTF-16 (o cualquier otra codificación) de una arbitraria conjunto de bytes (ver UTF-16 on Wikipedia). Depende de usted, sin embargo, serializar y deserializar la matriz de bytes cifrados sin pérdida, para, por ejemplo, persistir y utilizarla más adelante. Aquí está el código de cliente modificada que debe darle una idea de lo que está sucediendo realmente con las matrices de bytes:

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

Ésta es la salida:

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 

El decryptedText es correcta, cuando se restaura desde el original encryptedByteArray . Tenga en cuenta que el valor encoded no es el mismo que encryptedByteArray, debido a la pérdida de datos durante la conversión byte[] -> String("UTF-16")->byte[].

+0

Gracias, por el momento encontré otra solución también (ver mi respuesta). Sin embargo, aceptaré su respuesta, porque su solución también funciona. – Bevor

+0

Hoy vi que descifrar la matriz de bytes original. En realidad, esto no es lo que quería (y desafortunadamente tuve un problema al descifrarlo cuando lo usé en diferentes máquinas virtuales), así que solo la solución de Joni funciona. – Bevor

+0

Mi código era solo una ilustración del error que tenía en su código, no una solución de serialización ("Depende de usted, sin embargo, serializar y deserializar la matriz de bytes cifrados sin ninguna pérdida"), ya que es posible que desee utilizar toneladas de diferentes técnicas de serialización (utilizando DataIn/OutputStreams, etc.) –

5

Ahora, he encontrado otra solución también ...

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

solución aceptada no funcionará si su String tiene algunas charcaters atípicos tales como š, ž, ć, Ō, ō, Ū, etc.

siguiente código trabajado muy bien para mí.

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

la única solución de trabajo, aunque tienes que depender de ApacheCommons – AntJavaDev

+3

Estaba usando 'android.util.Base64'. Si no desea incluir 'ApacheCommons', supongo que siempre puede copiar el archivo en su proyecto. Aquí está la fuente: https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Base64.java –

+1

sí señalé que para las aplicaciones Java, esta clase de utilidad también viene con JDK 1.8 , pero para las versiones anteriores de Java tienes que depender de ApacheCommons ya que es el único método de trabajo. – AntJavaDev

Cuestiones relacionadas