将 char 数组转换为字节数组并再次返回
Posted
技术标签:
【中文标题】将 char 数组转换为字节数组并再次返回【英文标题】:Converting char array into byte array and back again 【发布时间】:2011-06-23 08:14:23 【问题描述】:我希望在不创建中间String
的情况下将Java char 数组转换为字节数组,因为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<password.length; 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));
断言总是失败(而且,关键的是,当代码在生产中使用时,密码被拒绝),但打印语句打印出密码三次。为什么passwordBytes1AsString
和passwordBytes2AsString
与passwordAsString
不同,但看起来相同?我错过了一个空终止符还是什么?我该怎么做才能使转换和取消转换工作?
【问题讨论】:
为什么要避免创建中间字符串? Sun 建议将其作为最佳实践:download.oracle.com/javase/1.5.0/docs/guide/security/jce/… 字符串是不可变的,因此不能像 char 数组那样清零 - 相反,您的密码会在内存中停留不确定的时间。 【参考方案1】: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);
【讨论】:
【参考方案2】:问题在于您使用了String(byte[])
构造函数,它使用平台默认编码。这几乎是从不你应该做的——如果你传入“UTF-16”作为字符编码来工作,你的测试可能会通过。目前我怀疑passwordBytes1AsString
和passwordBytes2AsString
各有16个字符长,其他每个字符都是U+0000。
【讨论】:
我刚刚尝试过(即String passwordBytes1AsString = new String(passwordBytes1, "UTF-16");
)并且没有任何变化。我还尝试检查字符串的长度 - String.length()
返回 8。它会计算 U+0000 个字符吗?
@Scott:尝试打印出字符串的长度和单个字符(作为 int 值)。这会告诉你差异在哪里。
112,97,115,115,119,111,114,100 用于原始和转换后的。
刚刚注意到我在断言中对equals()
使用了错误的参数。 *facepalm* 您最初的假设确实是正确的。非常感谢。【参考方案3】:
原答案
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());
编辑为使用标准字符集
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());
这是JavaDoc page for StandardCharsets。 请在 JavaDoc 页面上注意这一点:
保证这些字符集在 Java 平台的每个实现中都可用。
【讨论】:
很好地使用了 ByteBuffer。但是,如果没有另外说明,密码是 Unicode,因此 StandardCharset.UTF_8 比通过将数据还原为 ASCII 来破坏数据要好。 你可以使用任何你需要的字符集 我已将帖子从 US-ASCII 更改为 UTF-8。你说的对。想法是保持相同的编码。例如,US-ASCII 没有 UTF-8 那么多的字符 - 没有带重音的字母,如果您使用第一个 UTF-8 并在 US-ASCII 之后使用,您会丢失一些信息。 在 char[] 或 byte[] 中存储敏感数据后,您需要清除敏感数据,正如 Andrii 在使用中解释的那样,从这里 ***.com/a/9670279/1582089 很好的例子。但就我而言,它适用于 Charset charset = Charset.forName("ISO-8859-1");【参考方案4】:我会做的是使用一个循环来转换为字节和另一个转换回字符。
char[] chars = "password".toCharArray();
byte[] bytes = new byte[chars.length*2];
for(int i=0;i<chars.length;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.length;i++)
chars2[i] = (char) ((bytes[i*2] << 8) + (bytes[i*2+1] & 0xFF));
String password = new String(chars2);
【讨论】:
【参考方案5】:如果您想使用 ByteBuffer 和 CharBuffer,请不要执行简单的 .asCharBuffer()
,它只是执行 UTF-16(LE 或 BE,取决于您的系统 - 您可以使用 @ 设置字节顺序987654322@ 方法)转换(因为 Java 字符串以及您的 char[]
在内部使用这种编码)。
使用Charset.forName(charsetName)
,然后使用encode
或decode
方法,或newEncoder
/newDecoder
。
当你把你的 byte[] 转换成 String 时,你也应该指明编码(并且应该是一样的)。
【讨论】:
【参考方案6】:这是对 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]
范围内的字符会受到影响。
【讨论】:
【参考方案7】:你应该使用getBytes()
而不是toCharArray()
换行
char[] password = "password".toCharArray();
与
byte[] password = "password".getBytes();
【讨论】:
不要在没有指定编码的情况下使用String#getBytes()
,这会让你陷入各种移植问题。
不适合用例:这一行只是在本例中获取 char[] 的一种简单方法。【参考方案8】:
当您在 Java 中使用 GetBytes From a String 时,返回结果将取决于您计算机设置的默认编码。(例如:StandardCharsetsUTF-8 或 StandardCharsets.ISO_8859_1 等...)。
所以,无论何时你想从一个字符串对象中获取字节。确保提供编码。喜欢:
String sample = "abc";
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8);
让我们检查一下代码发生了什么。 在 java 中,名为 sample 的字符串由 Unicode 存储。 String 中的每个字符存储 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
为了得到字符串的 oringle 字节。我们可以直接读取字符串的内存,得到字符串的每个字节。下面是示例代码:
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<chars.length;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[]) 时应该注意什么。希望对您有所帮助。以上是关于将 char 数组转换为字节数组并再次返回的主要内容,如果未能解决你的问题,请参考以下文章