【问题标题】:No sense length() result没有感觉长度()结果
【发布时间】:2021-10-01 04:41:01
【问题描述】:

从今天开始,我遇到了一个与 byte[] 到字符串转换相关的非常奇怪的错误。

代码如下:

private static final byte[] test_key = {-112, -57, -45, 125, 91, 126, -118, 13, 83, -60, -119, 57, 38, 118, -115, -52, -92, 39, -24, 75, 59, -21, 88, 84, 66, -125};

public static void main(String[] args) {
    byte[] encryptedArray = xor("ciao".getBytes(), test_key);

    System.out.println("Encrypted arrray: " + Arrays.toString(encryptedArray));
    final String encrypted = new String(encryptedArray);

    System.out.println("Length: " + new String(encryptedArray).length());
    System.out.println(Arrays.toString(encrypted.getBytes()));

    System.out.println("Encrypted value: " + encrypted);
    System.out.println("Decrypted value: " + new String(xor(encrypted.getBytes(), test_key)));
}

private static byte[] xor(byte[] data, byte[] key) {
    byte[] result = new byte[data.length];
    for (int i = 0; i < data.length; i++) {
        result[i] = (byte) (data[i] ^ key[i % key.length]);
    }
    return result;
}

我的输出是:

Encrypted arrray: [-13, -82, -78, 18]
Length: 2
[-17, -65, -67, 18]
Encrypted value: �
Decrypted value: xno

为什么 length() 返回 2?我错过了什么?

【问题讨论】:

  • 将密文转换为字符串并返回byte[] 时,您应用字符集编码。由于您没有定义特殊编码(顺便说一句,这很糟糕,应该始终指定编码!),因此使用默认编码,这显然会破坏密文。我可以使用 UTF-8 编码重现您的结果。对于任意二进制数据(如密文)的转换,必须应用像 Base64 这样的二进制到文本编码。或者,使用二进制数据,即直接解密encryptedArray而不是encrypted.getBytes()
  • 不使用base64和不直接解密encryptedArray就没有办法得到正确的输出?
  • 您还可以应用字符集编码,在字节和字符之间进行 1:1 映射,例如ISO-8859-1。看看here。为此,您必须相应地设置默认编码或为每个编码 (getBytes()) / 解码 (new String()) 明确指定编码。但这更像是一种解决方法,而不是长期解决方案。
  • Java 不是 C。不要使用字符串来保存任意字节序列。这就是字节数组的用途。

标签: java arrays encryption type-conversion xor


【解决方案1】:

字节和字符之间没有一对一的映射,而是取决于您使用的字符集。字符串在逻辑上是字符序列。所以如果要在chars和bytes之间进行转换,就需要一个字符编码,它指定chars到bytes的映射,反之亦然。 encryptedArray 中的字节首先转换为 Unicode 字符串,它会尝试从这些字节创建 UTF-8 字符序列。

如果您想使用 String 并还原回确切的字节,您需要对 encryptedArray 执行 Base64,然后对其执行 new String()

String encoded = new String(Base64.getEncoder().encode(encryptedArray));

要检索,只需解码:

Base64.getDecoder().decode(encoded);

【讨论】:

    【解决方案2】:

    我只是想到了一个很好的方法来显示发生了什么,只需将new String(byte[]) 方法替换为另一个方法,这就是我要回答这个问题的原因。这个执行与构造函数相同的基本操作,但有一个变化:如果发现任何无效字符,它将引发异常。

    private static final byte[] test_key = {-112, -57, -45, 125, 91, 126, -118, 13, 83, -60, -119, 57, 38, 118, -115, -52, -92, 39, -24, 75, 59, -21, 88, 84, 66, -125};
    
    public static void main(String[] args) throws Exception {
        byte[] encryptedArray = xor("ciao".getBytes(), test_key);
    
        System.out.println("Encrypted arrray: " + Arrays.toString(encryptedArray));
        final String encrypted = new String(encryptedArray);
    
        // original
        System.out.println("Length: " + new String(encryptedArray).length());
        
        // replacement
        System.out.println("Length: " + decode(encryptedArray).length());
        
        
        System.out.println(Arrays.toString(encrypted.getBytes()));
    
        System.out.println("Encrypted value: " + encrypted);
        System.out.println("Decrypted value: " + new String(xor(encrypted.getBytes(), test_key)));
    }
    
    private static String decode(byte[] encryptedArray) throws CharacterCodingException {
        var decoder = Charset.defaultCharset().newDecoder();
        decoder.onMalformedInput(CodingErrorAction.REPORT);
        var decoded = decoder.decode(ByteBuffer.wrap(encryptedArray));
        return decoded.toString();
    }
    
    private static byte[] xor(byte[] data, byte[] key) {
        byte[] result = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            result[i] = (byte) (data[i] ^ key[i % key.length]);
        }
        return result;
    }
    

    该方法被称为decode,因为这就是您实际在做的事情:您正在将字节解码 为文本。字符编码就是将字符编码为字节,也就是说反面的一定是解码。

    如您所见,上面将首先打印出2如果您的平台使用默认的 UTF-8 编码(Linux、Android、MacOS)。您可以通过在使用 Windows-1252 字符集的 Windows 上将 Charset.defaultCharset() 替换为 StandardCharsets.UTF_8 来获得相同的结果(单字节编码,它是 Latin-1 的扩展,它本身是 ASCII 的扩展)。但是如果你使用decode方法会产生如下异常:

    java.nio.charset.MalformedInputException: Input length = 3
        at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:274)
        at java.base/java.nio.charset.CharsetDecoder.decode(CharsetDecoder.java:815)
        at StackExchange/com.stackexchange.so.ShowBadEncoding.decode(ShowBadEncoding.java:36)
        at StackExchange/com.stackexchange.so.ShowBadEncoding.main(ShowBadEncoding.java:24) 
    

    现在您可能会在这里期望 4,即字节数组的大小。但请注意,UTF-8 字符可能会被编码为多个字节。错误不是发生在整个字符串上,而是发生在它试图读取的最后一个字符上。显然,它期望基于先前的字节值进行更长的编码。

    如果您将REPORT 替换为默认解码操作REPLACE(呵呵),您将看到结果与构造函数相同,length() 现在将再次返回值 2。

    当然,Topaco is correct 当他说你需要使用 base 64 编码时。这字节编码为字符,以便保留字节的所有含义,反过来当然是将文本解码回字节。

    【讨论】:

      【解决方案3】:

      String 的元素不是字节,而是字符。 char 不是字节。

      有很多方法可以将 char 转换为字节序列(即,许多字符集编码)。

      不是每个字符序列都可以转换为字节序列;每个字符并不总是有一个映射。这取决于您选择的字符集编码。

      不是每个字节序列都可以转换为字符串;字节必须在语法上对指定的字符集有效。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-08
        • 1970-01-01
        • 1970-01-01
        • 2022-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多