Comment faire au niveau du bit XOR opération sur deux chaînes en Java.
Vous voulez quelque chose comme ça:
import Sun.misc.BASE64Decoder;
import Sun.misc.BASE64Encoder;
import Java.io.IOException;
public class StringXORer {
public String encode(String s, String key) {
return base64Encode(xorWithKey(s.getBytes(), key.getBytes()));
}
public String decode(String s, String key) {
return new String(xorWithKey(base64Decode(s), key.getBytes()));
}
private byte[] xorWithKey(byte[] a, byte[] key) {
byte[] out = new byte[a.length];
for (int i = 0; i < a.length; i++) {
out[i] = (byte) (a[i] ^ key[i%key.length]);
}
return out;
}
private byte[] base64Decode(String s) {
try {
BASE64Decoder d = new BASE64Decoder();
return d.decodeBuffer(s);
} catch (IOException e) {throw new RuntimeException(e);}
}
private String base64Encode(byte[] bytes) {
BASE64Encoder enc = new BASE64Encoder();
return enc.encode(bytes).replaceAll("\\s", "");
}
}
L'encodage base64 est effectué car le xor'ing des octets d'une chaîne peut ne pas restituer des octets valides pour une chaîne.
Remarque: cela ne fonctionne que pour les caractères faibles, c'est-à-dire en dessous de 0x8000, cela fonctionne pour tous les caractères ASCII.
Je ferais un XOR chaque charAt () pour créer une nouvelle chaîne. Comme
String s, key;
StringBuilder sb = new StringBuilder();
for(int i = 0; i < s.length(); i++)
sb.append((char)(s.charAt(i) ^ key.charAt(i % key.length())));
String result = sb.toString();
En réponse au commentaire de @ user467257
Si votre entrée/sortie est utf-8 et vous xor "a" et "æ", vous vous retrouvez avec une chaîne utf-8 invalide composée d'un caractère (décimal 135, un caractère de continuation).
Ce sont les valeurs char
qui sont xor'ed, mais les valeurs d'octet et cela produit un caractère qui peut être encodé en UTF-8.
public static void main(String... args) throws UnsupportedEncodingException {
char ch1 = 'a';
char ch2 = 'æ';
char ch3 = (char) (ch1 ^ ch2);
System.out.println((int) ch3 + " UTF-8 encoded is " + Arrays.toString(String.valueOf(ch3).getBytes("UTF-8")));
}
impressions
135 UTF-8 encoded is [-62, -121]
Faites attention:
Un Java char
correspond à une unité de code UTF-16 et, dans certains cas, deux char
s consécutifs (un soi-disant une paire de substitution ) est nécessaire pour un caractère Unicode réel (point de code).
XORing deux séquences UTF-16 valides (ie Java Strings char
by char
, ou octet par octet après encodage en UTF-16) ne vous donne pas nécessairement une autre chaîne UTF-16 valide - vous pouvez avoir des substituts non appariés en conséquence. (Ce serait toujours une chaîne parfaitement utilisable Java String, seules les méthodes concernant le point de code pourraient se confondre, et celles qui convertir en d'autres encodages pour la sortie et similaire.)
La même chose est valable si vous convertissez d'abord vos chaînes en UTF-8 puis XOR ces octets - ici vous très probablement se retrouvera avec une séquence d'octets qui n'est pas UTF-8 valide, si vos chaînes n'étaient pas déjà toutes les deux pures ASCII chaînes.
Même si vous essayez de le faire correctement et d'itérer sur vos deux chaînes par point de code et essayez de XOR les points de code, vous pouvez vous retrouver avec des points de code en dehors de la plage valide (par exemple, U+FFFFF
(avion 15) XOR U+10000
(avion 16) = U+1FFFFF
(qui serait le dernier caractère de l'avion 31), bien au-dessus de la plage des points de code existants. Et vous pourriez également vous retrouver ainsi avec des points de code réservés aux substituts (= non valides).
Si vos chaînes ne contiennent que des caractères <128, 256, 512, 1024, 2048, 4096, 8192, 16384 ou 32768, alors les chaînes XOR (en termes de caractères) seront dans la même plage et ne contiendront donc certainement aucun substitut. Dans les deux premiers cas, vous pouvez également coder votre chaîne en tant que ASCII ou Latin-1, respectivement, et avoir le même résultat XOR pour les octets. (Vous pouvez toujours vous retrouver avec des caractères de contrôle, ce qui peut être un problème pour vous.)
Ce que je dis enfin ici: ne vous attendez pas à ce que le résultat du cryptage des chaînes soit à nouveau une chaîne valide - au lieu de cela, stockez-la simplement et transmettez-la en tant que byte[]
(ou un flux d'octets). (Et oui, convertissez-vous en UTF-8 avant le cryptage, et en UTF-8 après le décryptage).
Voici le code que j'utilise:
private static byte[] xor(final byte[] input, final byte[] secret) {
final byte[] output = new byte[input.length];
if (secret.length == 0) {
throw new IllegalArgumentException("empty security key");
}
int spos = 0;
for (int pos = 0; pos < input.length; ++pos) {
output[pos] = (byte) (input[pos] ^ secret[spos]);
++spos;
if (spos >= secret.length) {
spos = 0;
}
}
return output;
}
En supposant (!) Que les chaînes sont de longueur égale, pourquoi pas convertir les chaînes en tableaux d'octets puis XOR les octets. Les tableaux d'octets résultants peuvent être de longueurs différentes également en fonction de votre encodage (par exemple, UTF8 s'étendra à différentes longueurs d'octets pour différents caractères).
Vous devez faire attention à spécifier l'encodage des caractères pour assurer une conversion chaîne/octet cohérente/fiable.
Cette solution est compatible avec Android (je l'ai testé et utilisé moi-même). Merci à @ user467257 dont j'ai adapté la solution depuis.
import Android.util.Base64;
public class StringXORer {
public String encode(String s, String key) {
return new String(Base64.encode(xorWithKey(s.getBytes(), key.getBytes()), Base64.DEFAULT));
}
public String decode(String s, String key) {
return new String(xorWithKey(base64Decode(s), key.getBytes()));
}
private byte[] xorWithKey(byte[] a, byte[] key) {
byte[] out = new byte[a.length];
for (int i = 0; i < a.length; i++) {
out[i] = (byte) (a[i] ^ key[i%key.length]);
}
return out;
}
private byte[] base64Decode(String s) {
return Base64.decode(s,Base64.DEFAULT);
}
private String base64Encode(byte[] bytes) {
return new String(Base64.encode(bytes,Base64.DEFAULT));
}
}
la fonction abs est lorsque les chaînes n'ont pas la même longueur, la longueur du résultat sera donc la même que la longueur minimale des deux chaînes a et b
public String xor(String a, String b){
StringBuilder sb = new StringBuilder();
for(int k=0; k < a.length(); k++)
sb.append((a.charAt(k) ^ b.charAt(k + (Math.abs(a.length() - b.length()))))) ;
return sb.toString();
}