将 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));

断言总是失败(而且,关键的是,当代码在生产中使用时,密码被拒绝),但打印语句打印出密码三次。为什么passwordBytes1AsStringpasswordBytes2AsStringpasswordAsString 不同,但看起来相同?我错过了一个空终止符还是什么?我该怎么做才能使转换和取消转换工作?

【问题讨论】:

为什么要避免创建中间字符串? 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”作为字符编码来工作,你的测试可能会通过。目前我怀疑passwordBytes1AsStringpasswordBytes2AsString各有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),然后使用encodedecode 方法,或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));

我们需要在使用 (&amp; 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 数组转换为字节数组并再次返回的主要内容,如果未能解决你的问题,请参考以下文章

字节数组到短数组并在java中再次返回

将字节数组转换为字符串并返回字节数组的问题

如何将char数组转换为字节数组?

如何将浮点数转换为长度为 4 的字节数组(char* 数组)?

从字节数组转换为 base64 并返回

如何将字节从char数组转换为int