4

我想存储一个包装在 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);

但这没有帮助。

有任何想法吗?

4

7 回答 7

18

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

事实上,我刚刚找到了一个用于 Java 的公共域 base64 编码器:http: //iharder.sourceforge.net/current/java/base64/

于 2009-08-18T19:25:09.277 回答
11

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

让我们一点一点地分解它:

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

Windows-1252如果您的系统的默认字符编码是oriso-8859-1或,此时您的字节数组很可能包含 8 个字节UTF-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);

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

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

于 2009-08-18T19:44:33.310 回答
4

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

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

于 2009-08-18T19:27:02.970 回答
3

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

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

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

另外,您为什么要加密密码而不是用盐对它们进行散列?

于 2009-08-18T19:26:09.367 回答
2

Implement a StringWrapper class whose constructor takes a String arg and coverts it to a byte[]. Use "ISO-8859-1" encoding to ensure each char will be just 8 bits instead of 16. You can then obviously use encoding/decoding methods to manipulate those bytes.

于 2012-05-18T00:31:27.597 回答
1

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

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

于 2009-08-18T19:28:11.297 回答
0

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

于 2009-08-18T19:25:57.340 回答