我正在寻找将Java char数组转换为字节数组而不创建中间String的方法,因为char数组包含密码。 我查看了几种方法,但是它们似乎都失败了:
char[] password ="password".toCharArray();
byte[] passwordBytes1 = new byte[password.length*2];
ByteBuffer.wrap(passwordBytes1).asCharBuffer().put(password);
byte[] passwordBytes2 = new byte[password.length*2];
for(int i=0; i
passwordBytes2[2*i] = (byte) ((password[i]&0xFF00)>>8);
passwordBytes2[2*i+1] = (byte) (password[i]&0x00FF);
}
String passwordAsString = new String(password);
String passwordBytes1AsString = new String(passwordBytes1);
String passwordBytes2AsString = new String(passwordBytes2);
System.out.println(passwordAsString);
System.out.println(passwordBytes1AsString);
System.out.println(passwordBytes2AsString);
assertTrue(passwordAsString.equals(passwordBytes1) || passwordAsString.equals(passwordBytes2));
断言总是失败的(并且,至关重要的是,当在生产中使用该代码时,密码被拒绝),但是print语句会打印出三次密码。 为什么passwordBytes1AsString和passwordBytes2AsString与passwordAsString不同,却显得相同? 我是否错过了空终止符之类的东西? 我怎样做才能使转换和未转换工作?
为什么要避免创建中间字符串?
Sun建议将它作为最佳实践:download.oracle.com/javase/1.5.0/docs/guide/security/jce/字符串是不可变的,因此不能像char数组一样清零-相反,您的密码在内存中徘徊, 不确定的时间。
char和byte之间的转换是字符集的编码和解码。我更喜欢在代码中尽可能地使其清晰。这实际上并不意味着额外的代码量:
Charset latin1Charset = Charset.forName("ISO-8859-1");
charBuffer = latin1Charset.decode(ByteBuffer.wrap(byteArray)); // also decode to String
byteBuffer = latin1Charset.encode(charBuffer); // also decode from String
在旁边:
java.nio类和java.io Reader / Writer类使用ByteBuffer和CharBuffer(它们使用byte []和char []作为后备数组)。因此,通常最好直接使用这些类。但是,您始终可以执行以下操作:
byteArray = ByteBuffer.array(); byteBuffer = ByteBuffer.wrap(byteArray);
byteBuffer.get(byteArray); charBuffer.put(charArray);
charArray = CharBuffer.array(); charBuffer = ByteBuffer.wrap(charArray);
charBuffer.get(charArray); charBuffer.put(charArray);
问题是您使用String(byte[])构造函数,该构造函数使用平台默认编码。这几乎从来都不是您应该做的-如果您通过" UTF-16"作为字符编码正常工作,则测试可能会通过。目前,我怀疑passwordBytes1AsString和passwordBytes2AsString的长度均为16个字符,其他每个字符均为U + 0000。
我只是尝试了(即String passwordBytes1AsString = new String(passwordBytes1,"UTF-16");)而没有任何变化。我还尝试检查字符串的长度-String.length()返回8。它将计算U + 0000个字符吗?
@Scott:尝试打印出字符串的长度以及各个字符(作为int值)。 Thatll向您显示差异之处。
原始版本和转换版本都为112,97,115,115,119,111,114,100。
@斯科特:在那种情况下,断言现在应该过去了...
刚刚注意到我在断言中对equals()使用了错误的参数。 * facepalm *您最初的假设确实是正确的。非常感谢。
原始答案
public byte[] charsToBytes(char[] chars){
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode(CharBuffer.wrap(chars));
return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
}
public char[] bytesToChars(byte[] bytes){
Charset charset = Charset.forName("UTF-8");
CharBuffer charBuffer = charset.decode(ByteBuffer.wrap(bytes));
return Arrays.copyOf(charBuffer.array(), charBuffer.limit());
}
编辑以使用StandardCharsets
public byte[] charsToBytes(char[] chars)
{
final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars));
return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
}
public char[] bytesToChars(byte[] bytes)
{
final CharBuffer charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
return Arrays.copyOf(charBuffer.array(), charBuffer.limit());
}
这是StandardCharsets的JavaDoc页面。
在JavaDoc页面上注意这一点:
These charsets are guaranteed to be available on every implementation of the Java platform.
很好地使用ByteBuffer。但是,如果没有另外说明,密码是Unicode,因此StandardCharset.UTF_8优于通过将数据简化为ASCII来破坏数据。
您可以使用所需的任何字符集
我的观点是,ASCII通常是错误的。
我编辑了从US-ASCII更改为UTF-8的帖子。你是对的。想法是保持相同的编码。例如,US-ASCII的字符数不及UTF-8的字符数-没有带重音符号的字母,如果使用的是第一个UTF-8,然后使用US-ASCII,则会丢失一些信息。
将敏感数据存储在char []或byte []中后,您需要清除敏感数据,如Andrii在此处说明的用法一样stackoverflow.com/a/9670279/1582089
很好的例子。但就我而言,它可以与Charset charset = Charset.forName(" ISO-8859-1");一起使用。
我要做的是使用一个循环将其转换为字节,然后将另一个转换为char。
char[] chars ="password".toCharArray();
byte[] bytes = new byte[chars.length*2];
for(int i=0;i
bytes[i*2] = (byte) (chars[i] >> 8);
bytes[i*2+1] = (byte) chars[i];
}
char[] chars2 = new char[bytes.length/2];
for(int i=0;i
chars2[i] = (char) ((bytes[i*2] << 8) + (bytes[i*2+1] & 0xFF));
String password = new String(chars2);
如果要使用ByteBuffer和CharBuffer,请不要执行简单的.asCharBuffer(),它只是执行UTF-16(LE或BE,具体取决于您的系统-您可以使用order方法设置字节顺序)转换(由于Java字符串,因此您的char[]在内部使用此编码)。
使用Charset.forName(charsetName),然后使用其encode或decode方法或newEncoder / newDecoder。
将byte []转换为String时,还应指出编码方式(且应相同)。
这是彼得·劳瑞(Peter Lawrey)答案的延伸。为了在整个字符范围内正确进行向后(字节到字符)转换,代码应如下所示:
char[] chars = new char[bytes.length/2];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) (((bytes[i*2] & 0xff) << 8) + (bytes[i*2+1] & 0xff));
}
我们需要在使用(& 0xff)之前对字节进行"取消签名"。否则,所有可能的char值的一半将无法正确返回。例如,[0x80..0xff]范围内的字符将受到影响。
您应该使用getBytes()而不是toCharArray()
更换线
char[] password ="password".toCharArray();
同
byte[] password ="password".getBytes();
不要在未指定编码的情况下使用String#getBytes(),这将使您陷入各种可移植性麻烦。
不适合用例:在此示例中,此行只是获取char []的简单方法。
当您使用Java中的"从字符串获取GetBytes"时,返回结果将取决于计算机设置的默认编码(例如:StandardCharsetsUTF-8或StandardCharsets.ISO_8859_1etc ...)。
因此,每当您想要从字符串对象获取字节数时。确保提供一个编码。喜欢 :
String sample ="abc";
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8);
让我们检查一下代码发生了什么。
在java中,名为sample的字符串由Unicode存储。字符串中的每个字符按2个字节存储。
sample : value:"abc" in Memory(Hex): 00 61 00 62 00 63
a -> 00 61
b -> 00 62
c -> 00 63
但是,当我们从字符串中获取字节时,
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8)
//result is : 61 62 63
//length: 3 bytes
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_16BE)
//result is : 00 61 00 62 00 63
//length: 6 bytes
为了获得字符串的原始字节。我们可以读取字符串的Memory并获取String的每个字节,下面是示例代码:
public static byte[] charArray2ByteArray(char[] chars){
int length = chars.length;
byte[] result = new byte[length*2+2];
int i = 0;
for(int j = 0 ;j
result[i++] = (byte)( (chars[j] & 0xFF00) >> 8 );
result[i++] = (byte)((chars[j] & 0x00FF)) ;
}
return result;
}
用途:
String sample ="abc";
//First get the chars of the String,each char has two bytes(Java).
Char[] sample_chars = sample.toCharArray();
//Get the bytes
byte[] result = charArray2ByteArray(sample_chars).
//Back to String.
//Make sure we use UTF_16BE. Because we read the memory of Unicode of
//the String from Left to right. That's the same reading
//sequece of UTF-16BE.
String sample_back= new String(result , StandardCharsets.UTF_16BE);
这个问题没有提到getBytes,所以这并没有真正的意义。您是否要评论其他答案之一?
只想声明String的getBytes函数的用法。并且在使用new String(Byte [])时应该注意什么。希望能帮助到你。