2011-02-08 14 views
34

Tôi đang tìm cách chuyển đổi mảng Java thành mảng byte mà không cần tạo một mã trung gian String, vì mảng char chứa mật khẩu. Tôi đã nhìn lên một vài phương pháp, nhưng tất cả dường như thất bại:Chuyển mảng char thành mảng byte và ngược lại

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

Khẳng định luôn thất bại (và, giới phê bình, khi mã được sử dụng trong sản xuất, mật khẩu bị từ chối), nhưng bản in báo cáo in ra mật khẩu ba lần. Tại sao passwordBytes1AsStringpasswordBytes2AsString khác với passwordAsString, nhưng xuất hiện giống hệt nhau? Tôi bỏ lỡ một terminator null hay cái gì? Tôi có thể làm gì để thực hiện công việc chuyển đổi và không chuyển đổi?

+0

Tại sao Bạn muốn tránh việc tạo ra một trung gian Chuỗi? – KarlP

+12

Sun đề xuất phương pháp này là phương pháp hay nhất: http://download.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx Strings không thay đổi và do đó không thể được xóa như mảng char - thay vào đó, mật khẩu của bạn bị treo trong bộ nhớ trong một khoảng thời gian không xác định. – Scott

Trả lời

12

Sự cố là việc bạn sử dụng hàm tạo String(byte[]), sử dụng mã hóa mặc định nền tảng. Đó là gần như không bao giờ những gì bạn nên làm - nếu bạn vượt qua "UTF-16" khi mã hóa ký tự hoạt động, các bài kiểm tra của bạn có thể sẽ vượt qua. Hiện tại tôi nghi ngờ rằng passwordBytes1AsStringpasswordBytes2AsString là mỗi 16 ký tự, với mỗi ký tự khác là U + 0000.

+0

Tôi vừa thử (ví dụ: 'String passwordBytes1AsString = new String (passwordBytes1," UTF-16 ");') và không có thay đổi. Tôi cũng đã thử kiểm tra độ dài của chuỗi - 'String.length()' trả về 8. Nó có đếm số ký tự U + 0000 không? – Scott

+0

@Scott: Thử in ra độ dài của chuỗi và các ký tự riêng lẻ (dưới dạng giá trị int). Điều đó sẽ cho bạn thấy sự khác biệt ở đâu. –

+0

112,97,115,115,119,111,114,100 cho cả bản gốc và bộ chuyển đổi. – Scott

4

Nếu bạn muốn sử dụng ByteBuffer và CharBuffer, đừng làm đơn giản .asCharBuffer(), đơn giản là thực hiện UTF-16 (LE hoặc BE, tùy thuộc vào hệ thống của bạn - bạn có thể đặt thứ tự byte bằng phương pháp order) chuyển đổi (kể từ khi các chuỗi Java và do đó char[] nội bộ của bạn sử dụng mã hóa này).

Sử dụng Charset.forName(charsetName) và sau đó là phương pháp encode hoặc decode hoặc newEncoder/newDecoder.

Khi chuyển đổi byte [] thành Chuỗi, bạn cũng nên chỉ ra mã hóa (và nó phải giống nhau).

4

Tôi sẽ làm là sử dụng vòng lặp để chuyển đổi thành byte và một vòng lặp khác để chuyển đổi thành 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); 
2

Bạn nên tận dụng getBytes() thay vì toCharArray()

Thay thế dòng

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

với

byte[] password = "password".getBytes(); 
+5

không sử dụng 'String # getBytes()' mà không chỉ định mã hóa, điều này sẽ đưa bạn vào tất cả các loại sự cố về tính di động. – eckes

+0

không phù hợp với trường hợp sử dụng: dòng này chỉ là một cách dễ dàng để lấy char [] trong ví dụ này. – Cerber

11

chuyển đổi giữa char và byte là bộ ký tự mã hóa và giải mã. Tôi thích làm cho nó càng rõ ràng càng tốt de. Nó không thực sự có nghĩa là thêm khối lượng mã:

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 

Ngoài: lớp java.nio

và Reader java.io/classes Writer sử dụng ByteBuffer & CharBuffer (mà sử dụng byte [] và char [ ] như mảng sao lưu). Vì vậy, thường thích hợp hơn nếu bạn sử dụng các lớp này trực tiếp. Tuy nhiên, bạn luôn có thể làm:

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

Đây là phần mở rộng cho câu trả lời của Peter Lawrey.Để việc chuyển đổi ngược (byte-to-chars) một cách chính xác cho toàn bộ phạm vi của chars, mã nên được như sau:

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

Chúng ta cần phải "unsign" byte trước khi sử dụng (& 0xff). Nếu không, một nửa của tất cả các giá trị char có thể sẽ không lấy lại chính xác. Ví dụ: ký tự trong phạm vi [0x80..0xff] sẽ bị ảnh hưởng.

1

Khi bạn sử dụng GetBytes từ chuỗi trong Java, kết quả trả về sẽ phụ thuộc vào mã hóa mặc định của cài đặt máy tính của bạn (ví dụ: StandardCharsetsUTF-8 hoặc StandardCharsets.ISO_8859_1etc ...).

Vì vậy, bất cứ khi nào bạn muốn getBytes từ một đối tượng chuỗi. Đảm bảo cung cấp mã hóa. như:

String sample = "abc"; 
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8); 

Hãy kiểm tra xem điều gì đã xảy ra với mã. Trong java, String có tên mẫu, được lưu trữ bởi Unicode. mỗi char trong String được lưu trữ bởi 2 byte.

sample : value: "abc" in Memory(Hex): 00 61 00 62 00 63 
     a -> 00 61 
     b -> 00 62 
     c -> 00 63 

Nhưng, Khi chúng tôi getBytes Từ một String, chúng tôi có

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 

Để có được byte oringle của String. Chúng tôi chỉ có thể đọc bộ nhớ của chuỗi và nhận Mỗi byte của String.Below là Bộ luật mẫu:

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

Công dụng:

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

Câu hỏi không đề cập đến 'getBytes', vì vậy điều này không thực sự có liên quan. Bạn đang cố gắng bình luận về một trong những câu trả lời khác? –

+0

Chỉ muốn khai báo rằng tập quán của hàm getBytes của String. Và những gì nên được chăm sóc khi sử dụng String mới (Byte []). Hy vọng nó giúp. –

4
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());  
    } 
+1

Sử dụng tốt ByteBuffer. Tuy nhiên, nếu không có tuyên bố khác, mật khẩu là Unicode, vì vậy StandardCharset.UTF_8 sẽ tốt hơn là làm hỏng dữ liệu bằng cách giảm nó thành ASCII. –

+0

Bạn có thể sử dụng bất kỳ bộ ký tự nào bạn cần – Cassian

+0

Điểm của tôi là ASCII thường rất sai. –