将字节数组转换为字符串并返回字节数组的问题

Posted

技术标签:

【中文标题】将字节数组转换为字符串并返回字节数组的问题【英文标题】:Problems converting byte array to string and back to byte array 【发布时间】:2012-02-24 06:18:40 【问题描述】:

这个主题有很多问题,相同的解决方案,但这对我不起作用。我有一个简单的加密测试。加密/解密本身有效(只要我用字节数组本身而不是字符串来处理这个测试)。问题是不想将其作为字节数组而是作为字符串处理,但是当我将字节数组编码为字符串并返回时,生成的字节数组与原始字节数组不同,因此解密不再起作用。我在对应的字符串方法中尝试了以下参数: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);
    

【问题讨论】:

您首先需要将字节转换为可以显示为字符串的内容。通常通过转换为 hex 或 base64。 在转换为字符串之前和之后,您在字节数组中看到的实际差异是什么? @Roger Lindsjö:感谢您的提示。我马上试试。 @Herms: 一个例子 -> encryptedByteArray:[B@7df17e77, 编码:[B@79a5f739 那些看起来像内存地址,而不是数组的实际内容。 【参考方案1】:

将加密数据存储在字符串中并不是一个好主意,因为它们用于人类可读的文本,而不是用于任意二进制数据。对于二进制数据,最好使用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);

其他常见的不会丢失数据的编码是 hexadecimalbase64,但遗憾的是您需要一个帮助程序库来处理它们。标准 API 没有为它们定义类。

使用 UTF-16 时,程序会因两个原因而失败:

    String.getBytes("UTF-16") 将字节顺序标记字符添加到输出以标识字节的顺序。您应该使用 UTF-16LE 或 UTF-16BE 以防止这种情况发生。 并非所有字节序列都可以映射到 UTF-16 中的字符。首先,以 UTF-16 编码的文本必须有偶数字节。其次,UTF-16 有一种机制可以对 U+FFFF 之外的 unicode 字符进行编码。这意味着例如有 4 个字节的序列仅映射到一个 unicode 字符。为此,4 个字节的前 2 个字节不编码 UTF-16 中的任何字符。

【讨论】:

今天看到我在不同的虚拟机中使用加密和解密时遇到问题。显然只有你的解决方案有效。 您使用 Apache Commons Codec 的方法也应该有效,但您必须将 commons-codec 库与您的应用程序一起分发。【参考方案2】:

如果您的String 包含一些非典型字符,例如š, ž, ć, Ō, ō, Ū 等,则接受的解决方案将不起作用。

以下代码对我来说效果很好。

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

【讨论】:

唯一可行的解​​决方案,尽管您必须依赖 ApacheCommons 我使用的是android.util.Base64。如果您不想包含ApacheCommons,我想您可以随时将文件复制到您的项目中。以下是来源:github.com/android/platform_frameworks_base/blob/master/core/… 是的,我指出对于 java 应用程序,这个实用程序类也随 JDK 1.8 一起提供,但对于以前的 java 版本,您必须依赖 ApacheCommons,因为它是唯一的工作方法。 我不知道如何开始感谢你。我非常非常非常感谢。更多代码给你的大脑。 另一种选择是使用javax.xml.bind.DatatypeConverter.printHexBinary(bytesToHexString[])javax.xml.bind.DatatypeConverter.parseHexBinary("hexStringTobytes")【参考方案3】:

现在,我也找到了另一个解决方案……

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

【讨论】:

【参考方案4】:

您的问题是您无法从任意字节数组构建 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

当从原始encryptedByteArray 恢复时,decryptedText 是正确的。请注意,encoded 的值与encryptedByteArray 的值不同,因为在byte[] -&gt; String("UTF-16")-&gt;byte[] 转换过程中会丢失数据。

【讨论】:

谢谢,目前我也找到了另一个解决方案(见我的回答)。不过我会接受你的回答,因为你的解决方案也有效。 今天看到你解密原始字节数组。这实际上不是我想要的(不幸的是,在不同的 VM 中使用它时再次解密时遇到了问题),所以只有 Joni 的解决方案有效。 我的代码只是说明您在代码中遇到的错误,而不是序列化解决方案(“但是,是否序列化和反序列化加密的字节数组没有任何损失取决于您”) ,因为您可能想要使用大量不同的序列化技术(使用 DataIn/OutputStreams 等)

以上是关于将字节数组转换为字符串并返回字节数组的问题的主要内容,如果未能解决你的问题,请参考以下文章

将 char 数组转换为字节数组并再次返回

Java将字节数组转换为十六进制字节数组[重复]

将范围为 -128 到 127 的字节数组转换为字符串数组

将表示 UCHAR 数组的 json 字符串响应转换为字节数组

如何将base64位的字节数组转换成图片并显示

如何在 Java 中将 libGDX 的纹理转换为字节数组并再次返回