【问题标题】:Java Strings storing byte arrays存储字节数组的 Java 字符串
【发布时间】:2009-08-18 19:22:58
【问题描述】:

我想存储一个包装在 String 对象中的字节数组。这是场景

  1. 用户输入密码。
  2. 该密码的字节是使用 getBytes() String 方法获取的。
  3. 它们的字节是使用 java 的 crypo 包加密的。
  4. 然后使用构造函数 new String(bytes[]) 将这些字节转换为字符串
  5. 该字符串已存储,或以其他方式传递(未更改)
  6. 获得该字符串的字节,它们与编码字节不同。

这里有一段代码描述了我在说什么。

String s = "test123";
byte[] a = s.getBytes();
byte[] b = env.encrypt(a);
String t = new String(b);
byte[] c = t.getBytes();
byte[] d = env.decrypt(c);

其中 env.encrypt() 和 env.decrypt() 进行加密和解密。我遇到的问题是 b 数组的长度为 8,而 c 数组的长度为 16。我认为它们是相等的。这里发生了什么?我尝试修改代码如下

String s = "test123";
Charset charset = Charset.getDefaultCharset();
byte[] a = s.getBytes(charset);
byte[] b = env.encrypt(a);
String t = new String(b, charset);
byte[] c = t.getBytes(charset);
byte[] d = env.decrypt(c);

但这并没有帮助。

有什么想法吗?

【问题讨论】:

  • 您的代码示例中的env 是什么?

标签: java string bytearray


【解决方案1】:

将二进制数据存储在 String 对象中并不是一个好主意。最好使用 Base64 编码之类的东西,它旨在将二进制数据转换为可打印的字符串,并且是完全可逆的。

其实我只是找到了一个Java的公共领域base64编码器:http://iharder.sourceforge.net/current/java/base64/

【讨论】:

  • +1 获取密码,加密,转换为base64字符串(最后一位建议使用Apache Commons Codec)。
  • 除非您绝对别无选择,否则将机密存储在 String 对象(密码输入或解密输出)中也不是一个好主意。这是因为没有办法清除字符串 - 一旦它在内存中,字符串不会被覆盖,直到内存被垃圾收集并且内存分配器决定重新分配那部分内存。
  • 请解释一下为什么将二进制数据存储在字符串 obj 中是个坏主意?我并不是说我不同意,但证实你的主张通常是个好主意。
  • String 类本身的 API 文档说“String 类表示字符串”。再往下,它还说“一个字符串代表一个 UTF-16 格式的字符串......”。基本上,它是一个用于存储字符数据的智能对象,而您需要一个用于存储二进制数据的哑对象。
【解决方案2】:

一些人指出这不是String(byte[]) 构造函数的正确使用。重要的是要记住,在 Java 中,String 由字符组成,而字符恰好是 16 位,而不是 8 位,就像一个字节一样。您也忘记了字符编码。请记住,字符通常不是字节。

让我们一点一点地分解:

String s = "test123";
byte[] a = s.getBytes();

此时如果您的系统的默认字符编码是Windows-1252iso-8859-1UTF-8,那么您的字节数组很可能包含8 个字节。

byte[] b = env.encrypt(a);

现在b 包含一些看似随机的数据,具体取决于您的加密,甚至不能保证一定长度。许多加密引擎会填充输入数据,以便输出匹配特定的块大小。

String t = new String(b);

这是获取您的随机字节并要求 Java 将它们解释为字符数据。这些字符可能看起来像乱码,并且某些位序列不是每种编码的有效字符。 Java 尽职尽责地创建了一个 16 位字符序列。

byte[] c = t.getBytes();

这可能会或可能不会为您提供与b 相同的字节数组,具体取决于编码。您在问题描述中声明您看到 c 为 16 个字节长;这可能是因为 t 中的垃圾在默认字符编码中不能很好地转换。

byte[] d = env.decrypt(c);

这不起作用,因为 c 不是您期望的数据,而是已损坏。

解决方案:

  1. 只需将字节数组直接存储在数据库中或任何地方。但是您仍然忘记了字符编码问题,稍后会详细介绍。
  2. 获取字节数组数据并使用 Base64 或十六进制数字对其进行编码并存储该字符串:

    byte[] cypherBytes = env.encrypt(getBytes(plainText));
    StringBuffer cypherText = new StringBuffer(cypherBytes.length * 2);
    for (byte b : cypherBytes) {
      String hex = String.format("%02X", b); //$NON-NLS-1$
      cypherText.append(hex);
    }
    return cypherText.toString();
    

字符编码:

用户的密码可能不是 ASCII,因此您的系统很容易出现问题,因为您没有指定编码。

比较:

String s = "tést123";
byte[] a = s.getBytes();
byte[] b = env.encrypt(a);

String s = "tést123";
byte[] a = s.getBytes("UTF-8");
byte[] b = env.encrypt(a);

字节数组aUTF-8 编码的值与系统默认值不同(除非您的系统默认值是UTF-8)。只要 A) 你是一致的并且 B) 你的编码可以代表你的数据的所有允许的字符,你使用什么编码并不重要。您可能无法以系统默认编码存储中文文本。如果您的应用程序曾经部署在多台计算机上,并且其中一台计算机具有不同的系统默认编码,则在一个系统上加密的密码在另一个系统上将变成乱码。

故事的寓意: 字符不是字节,字节也不是字符。您必须记住您正在处理的内容以及如何在它们之间来回转换。

【讨论】:

  • 感谢您提供的信息丰富的回答。我目前被第三方糟糕的类型选择所阻止;我需要通过他们的系统传递一个字符串,稍后我将在同一个 JVM 中返回,并且我需要使用字节。加密后的大小非常紧凑,我希望避免使用 Base64 编码。我希望有一个字符集说“每一位模式都是有效的”。
  • @CoryKendall 你在混合概念。许多字符编码具有“每个位模式都是有效的”。但在 Java 字符串中只有 UTF-16。
  • 啊,我明白了,所以我不能将任何位模式转换为 ISO-8859-1 字符串并转换回字节而不会看到变化吗?哪句话能用几行字回答这个问题?
  • @CoryKendall 关键是Java中没有ISO-8859-1字符串这样的东西。您可以有一个包含此类数据的字节数组,但不能包含 char[] 或 String。
【解决方案3】:

在这两种情况下,您都使用操作系统默认的非 Unicode 字符集(取决于区域设置)。如果您将字符串从一个系统传递到另一个系统,它们可能具有不同的语言环境,因此具有不同的默认字符集。你需要使用一个定义明确的字符集来做你想做的事情;例如ISO-8859-1。

更好的是,不要进行转换,直接传递byte[] 数组。

【讨论】:

    【解决方案4】:

    这有点滥用 String(byte[]) 构造函数和相关方法。

    这适用于某些编码,但不适用于其他编码。大概您平台的默认编码是其中一种失败的编码。

    您应该使用Commons Codec 之类的东西将这些字节转换为十六进制或base64。

    另外你为什么要加密密码而不是用盐散列它们?

    【讨论】:

      【解决方案5】:

      实现一个 StringWrapper 类,它的构造函数接受一个 String arg 并将其转换为一个 byte[]。使用“ISO-8859-1”编码确保每个字符只有 8 位而不是 16 位。然后您显然可以使用编码/解码方法来操作这些字节。

      【讨论】:

        【解决方案6】:

        这将无法正常工作。将字节存储为字符串仅适用于 ascii 集(以及其他一些)。如果您需要将加密结果存储为字符串,那么如何将字节转换为十六进制,然后将其放入字符串中。那会起作用的。

        我建议您将密码保留为字节。没有真正的理由将其存储为字符串(除非您想查看人们的密码是什么)。

        【讨论】:

          【解决方案7】:

          我没有给你一个明确的答案,但如果我正在处理这个问题,我会在每一步打印出字符串或字节并比较它们以查看发生了什么。此外,b 包含来自 env.encrypt 的返回值,但 c 是来自 .getBytes 的返回值,因此在这种情况下,您可以将苹果与橙子进行比较。

          【讨论】:

            猜你喜欢
            • 2017-04-08
            • 2013-07-04
            • 2011-10-04
            • 2014-02-12
            • 2017-04-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-07
            相关资源
            最近更新 更多