2012-02-01 82 views
30

本主题有很多问题,但解决方法相同,但这对我无效。我有一个简单的测试加密。加密/解密本身起作用(只要我用字节数组本身而不是字符串来处理这个测试)。问题是不希望将它作为字节数组处理,而是作为字符串来处理,但是当我将字节数组编码为字符串并返回时,生成的字节数组与原始字节数组不同,因此解密不再有效。我在相应的字符串方法中尝试了以下参数:UTF-8,UTF8,UTF-16,UTF8。他们都没有工作。得到的字节数组与原始数据不同。任何想法为何如此?将字节数组转换为字符串并返回到字节数组的问题

加密器:

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

您首先需要将字节转换为可以呈现为字符串的东西。通常通过转换为十六进制或base64。 – 2012-02-01 15:07:14

+0

在转换为字符串之前和之后,您在字节数组中看到的实际区别是什么? – Herms 2012-02-01 15:08:10

+0

@RogerLindsjö:感谢tipp。我会立即尝试。 – Bevor 2012-02-01 15:10:47

回答

73

将加密数据存储在字符串中不是一个好主意,因为它们是用于人类可读的文本,而不是任意的二进制数据。对于二进制数据,最好使用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以外的unicode字符的机制。这意味着例如有4个字节的序列只映射到一个Unicode字符。为了做到这一点,4的前2个字节不能用UTF-16编码任何字符。现在
+0

今天我看到我在使用不同VM的加密和解密时遇到了问题。显然只有你的解决方案有效。 – Bevor 2012-02-02 10:07:23

+0

您使用Apache Commons Codec的方法也应该可行,但您必须将commons-codec库与您的应用程序一起分发。 – Joni 2012-02-02 11:16:22

+0

非常有帮助,非常感谢:) – Spl2nky 2015-11-05 21:38:08

0

你的问题是,你不能从任意建立一个UTF-16(或任何其他编码)字符串字节数组(请参见UTF-16 on Wikipedia)。然而,由您决定序列化和反序列化加密的字节数组没有任何损失,以便保留它并稍后使用它。下面是应该给你什么实际使用的字节数组发生的一些见解修改后的客户端代码:

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[]转换期间数据丢失。

+0

谢谢,目前我还找到了另一个解决方案(请参阅我的答案)。不过,我会接受你的答案,因为你的解决方案也适用。 – Bevor 2012-02-01 16:02:18

+0

今天我看到你解密原始字节数组。这实际上并不是我想要的(不幸的是,当我在不同的虚拟机中使用它时,我再次解密时出现了问题),所以只有Joni的解决方案能够工作。 – Bevor 2012-02-02 10:06:13

+0

我的代码只是您在代码中出现的错误的示例,而不是序列化解决方案(然而,您应该使用序列化和反序列化加密的字节数组而无任何损失,这取决于您)吨不同的序列化技术(使用DataIn/OutputStreams等) – 2012-02-02 13:50:28

5

,我找到了另一种解决方案太...

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

如果您String具有如š, ž, ć, Ō, ō, Ū一些不典型charcaters接受的解决方案将无法正常工作等

下面的代码工作很适合我。

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

唯一的工作解决方案,虽然你必须依靠ApacheCommons – AntJavaDev 2016-01-13 13:43:34

+3

我正在使用'android.util.Base64'。如果你不想包含'ApacheCommons',我想你总是可以将文件复制到你的项目中。这里是来源:https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Base64.java – 2016-01-13 15:19:59

+1

是的,我指出,对于Java应用程序,此实用程序类也随JDK 1.8 ,但对于之前的Java版本,您必须依赖ApacheCommons,因为它是唯一的工作方法。 – AntJavaDev 2016-01-14 07:10:10

相关问题