2009-08-18 53 views
3

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

  1. 用户输入密码。
  2. 该密码的字节是使用getBytes()String方法获取的。
  3. 使用java的crypo软件包对它们的字节进行了加密。然后
  4. 这些字节被使用构造新的字符串(字节[])转换成字符串
  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); 

但这并没有帮助。

任何想法?

+0

代码示例中的'env'是什么? – 2009-08-18 19:25:28

回答

16

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

其实,我刚刚发现一个Java的公共领域的base64编码:http://iharder.sourceforge.net/current/java/base64/

+0

+1取得密码,加密它,转换为base64字符串(建议使用Apache Commons Codec的最后一位)。 – skaffman 2009-08-18 19:26:53

+2

除非你完全没有选择,否则在String对象中存储秘密(密码输入或解密输出)也不是一个好主意。这是因为没有办法清除一个字符串 - 一旦它存在内存中,一个字符串不会被覆盖,直到内存被垃圾回收并且内存分配器决定重新分配该内存段。 – atk 2009-08-18 19:49:19

+0

小心解释为什么将二进制数据存储在字符串obj中是个坏主意?我并不是说我不同意,但通常证明你的主张是个好主意。 – 2012-05-18 00:22:09

0

我没有给你一个明确的答案,但如果我是这方面的工作,我会打印出字符串或字节在每一步,并比较他们看看发生了什么。此外,b拥有env.encrypt的返回值,但c是.getBytes的返回值,所以在这种情况下,您可以将苹果与桔子进行比较。

3

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

这将使用某些编码,并与其他人一起失败。据推测,您的平台的默认编码是其中一个失败的编码。

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

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

4

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

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

2

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

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

11

有几个人指出,这是不是一个正确使用String(byte[])构造。重要的是要记住,在Java中,String由字符组成,字符恰好是16位,而不是8位,如字节所示。你也忘记了字符编码。请记住,一个字符通常不是一个字节。

让我们有点把它分解一下:

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

此时你的字节数组最有可能包含8个字节,如果你的系统的默认字符编码是Windows-1252iso-8859-1UTF-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. 采取字节数组数据,并使用基数64或作为十六进制数字编码,并存储该字符串:

    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)你的编码可以代表你的数据的所有允许字符。您可能无法将中文文本存储在系统默认编码中。如果您的应用程序曾部署在多台计算机上,并且其中一个计算机具有不同的系统默认编码,则在一个系统上加密的密码在另一个系统上会变得乱码。

道德故事: 字符不是字节,字节也不是字符。你必须记住你正在处理的是什么以及如何在它们之间来回转换。

+0

感谢您提供丰富的答案。我目前被第三方糟糕的类型选择所阻止;我需要通过他们的系统传递一个字符串,我将在稍后回到相同的JVM中,并且我需要使用字节。加密的大小非常紧凑,我希望避免Base64编码。我希望有一个字符集说“每一位模式是有效的”。 – 2014-09-05 20:51:06

+0

@CoryKendall你在混合概念。许多字符编码都有“每一位模式都是有效的”。但在Java字符串中只有UTF-16。 – 2014-09-05 21:07:36

+0

啊,我看到了,所以我不能将任何位模式转换为ISO-8859-1字符串,并返回字节而不会看到变化?哪几个会回答这个问题? – 2014-09-05 21:09:13

1

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