web-dev-qa-db-ja.com

文字配列をバイト配列に変換し、再び戻す

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

アサーションは常に失敗します(そして重大なことに、本番環境でコードが使用されるとパスワードが拒否されます)が、printステートメントはパスワードを3回出力します。なぜpasswordBytes1AsStringおよびpasswordBytes2AsStringpasswordAsStringとは異なるが、同じように見える? nullターミネーターなどがありませんか?変換と変換解除を機能させるにはどうすればよいですか?

36
Scott

問題は、プラットフォームのデフォルトのエンコーディングを使用するString(byte[])コンストラクターの使用です。それはほとんどneverすべきことです-動作する文字エンコーディングとして "UTF-16"を渡せば、テストはおそらくパスします。現在、私はpasswordBytes1AsStringおよびpasswordBytes2AsStringはそれぞれ16文字で、他のすべての文字はU + 0000です。

12
Jon Skeet

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);
14
Glen Best

元の回答

    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ページでこれに注意してください。

これらの文字セットは、Javaプラットフォームのすべての実装で使用できることが保証されています。

6
Cassian

ByteBufferとCharBufferを使用する場合は、単純な.asCharBuffer()を実行しないでください。UTF-16(システムに応じてLEまたはBE-を実行します。 orderメソッド)変換(Java文字列、したがって_char[]_は内部的にこのエンコーディングを使用するため))。

Charset.forName(charsetName)を使用してから、そのencodeまたはdecodeメソッド、またはnewEncoder/newDecoderを使用します。

Byte []をStringに変換するときは、エンコーディングも指定する必要があります(同じエンコーディングである必要があります)。

4
Paŭlo Ebermann

ループを使用してバイトに変換し、もう1つを使用してcharに変換します。

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);
4
Peter Lawrey

これは、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]の範囲内の文字が影響を受けます。

2
Vit Khudenko

getBytes()の代わりにtoCharArray()を使用する必要があります

行を置き換える

char[] password = "password".toCharArray();

byte[] password = "password".getBytes();
2
yoda

Javaで文字列からGetBytesを使用する場合、返される結果は、コンピューター設定のデフォルトのエンコードに依存します(例:StandardCharsetsUTF-8またはStandardCharsets.ISO_8859_1etc ...)。

したがって、文字列オブジェクトからBytesを取得したいときはいつでも。必ずencodeを指定してください。のような:

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

しかし、文字列からBytesを取得すると、

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

文字列の単一バイトを取得するため。文字列のメモリを読み取り、String.Belowの各バイトを取得するだけで、サンプルコードを取得できます。

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);
1
junqiang chen