web-dev-qa-db-fra.com

Conversion de tableau de caractères en tableau d'octets et inversement

Je cherche à convertir un tableau de caractères Java char en tableau d'octets sans créer un intermédiaire String, car le tableau char contient un mot de passe. J'ai recherché quelques méthodes, mais elles semblent toutes échouer:

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

L'assertion échoue toujours (et, de manière critique, lorsque le code est utilisé en production, le mot de passe est rejeté), mais les instructions d'impression impriment le mot de passe trois fois. Pourquoi passwordBytes1AsString et passwordBytes2AsString différent de passwordAsString, mais semble-t-il identique? Suis-je en train de manquer un terminateur nul ou quelque chose? Que puis-je faire pour que la conversion et la déconversion fonctionnent?

36
Scott

Le problème est votre utilisation du constructeur String(byte[]), qui utilise le codage par défaut de la plate-forme. C'est presque jamais ce que vous devriez faire - si vous passez "UTF-16" comme encodage de caractères pour fonctionner, vos tests réussiront probablement. Actuellement, je soupçonne que passwordBytes1AsString et passwordBytes2AsString ont chacun 16 caractères, chaque autre caractère étant U + 0000.

12
Jon Skeet

La conversion entre le caractère et l'octet est un encodage et un décodage de jeu de caractères. Je préfère le rendre aussi clair que possible dans le code. Cela ne signifie pas vraiment un volume de code supplémentaire:

 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

À part:

Les classes Java.nio et Java.io Reader/Writer utilisent ByteBuffer & CharBuffer (qui utilisent byte [] et char [] comme tableaux de sauvegarde). Si souvent préférable si vous utilisez ces classes directement. Cependant, vous pouvez toujours faire:

 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

Réponse originale

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

Modifié pour utiliser les jeux de caractères standard

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

Voici un page JavaDoc pour StandardCharsets . Notez ceci sur la page JavaDoc:

Ces jeux de caractères sont garantis pour être disponibles sur chaque implémentation de la plate-forme Java.

6
Cassian

Si vous souhaitez utiliser un ByteBuffer et un CharBuffer, ne faites pas la simple .asCharBuffer(), qui fait simplement un UTF-16 (LE ou BE, selon votre système - vous pouvez définir l'ordre des octets avec le Conversion de la méthode order) (puisque les chaînes Java et donc votre _char[]_ utilise en interne cet encodage).

Utilisez Charset.forName(charsetName), puis sa méthode encode ou decode, ou la newEncoder/newDecoder.

Lors de la conversion de votre octet [] en chaîne, vous devez également indiquer l'encodage (et il doit être le même).

4
Paŭlo Ebermann

Je ferais est d'utiliser une boucle pour convertir en octets et une autre pour reconvertir en 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

Ceci est une extension de la réponse de Peter Lawrey. Pour que la conversion en arrière (octets en caractères) fonctionne correctement pour toute la plage de caractères, le code doit être le suivant:

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

Nous devons "annuler la signature" des octets avant d'utiliser (& 0xff). Sinon, la moitié de toutes les valeurs de caractères possibles ne seront pas récupérées correctement. Par exemple, les caractères dans [0x80..0xff] la plage sera affectée.

2
Vit Khudenko

Vous devez utiliser getBytes() au lieu de toCharArray()

Remplacez la ligne

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

avec

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

Lorsque vous utilisez GetBytes à partir d'une chaîne en Java, le résultat de retour dépendra du codage par défaut de votre paramètre d'ordinateur (par exemple: StandardCharsetsUTF-8 ou StandardCharsets.ISO_8859_1etc ...).

Ainsi, chaque fois que vous souhaitez obtenir des octets à partir d'un objet chaîne. Assurez-vous de donner un code. comme :

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

Vérifions ce qui s'est passé avec le code. En Java, la chaîne nommée sample est stockée par Unicode. chaque caractère dans String stocké sur 2 octets.

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

Mais, lorsque nous obtenons des octets d'une chaîne, nous avons

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

Afin d'obtenir l'octet unique de la chaîne. Nous pouvons simplement lire la mémoire de la chaîne et obtenir chaque octet de la chaîne. Ci-dessous est l'exemple de code:

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

Coutumes:

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