Operación XOR con dos cadenas en Java

Cómo hacer una operación XOR bit a bit a dos cadenas en java.

Quieres algo como esto:

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

La encoding base64 se realiza porque xor'ing los bytes de una cadena no pueden dar bytes válidos para una cadena.

Nota: esto solo funciona para caracteres bajos, es decir, por debajo de 0x8000. Esto funciona para todos los caracteres ASCII.

Haría un XOR cada charAt () para crear un nuevo String. Me gusta

 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 respuesta al comentario de @ user467257

Si su entrada / salida es utf-8 y usted xor "a" y "æ", se queda con una cadena utf-8 no válida que consta de un carácter (decimal 135, un carácter de continuación).

Son los valores de char que se están xorizando, pero los valores de byte y esto produce un carácter codificado 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"))); } 

huellas dactilares

 135 UTF-8 encoded is [-62, -121] 

Presta atención:

Un char Java corresponde a una unidad de código UTF-16, y en algunos casos se necesitan dos caracteres consecutivos (un par sustituto ) para un carácter Unicode real (punto de código).

XORing dos secuencias UTF-16 válidas (es decir, cadenas de Java char por char , o byte por byte después de la encoding de UTF-16) no necesariamente le da otra cadena UTF-16 válida – puede tener sustitutos sin parear como resultado. (Todavía sería una cadena de Java perfectamente utilizable, solo el punto de código: los métodos podrían confundirse, y los que se convierten a otras codificaciones para la salida y similares).

Lo mismo es válido si primero convierte sus cadenas a UTF-8 y luego XOR a estos bytes; aquí es probable que termine con una secuencia de bytes que no es válida UTF-8, si sus cadenas ya no eran cadenas ASCII puras.

Incluso si trata de hacerlo bien e iterar sobre sus dos cadenas por punto de código e intentar XOR los puntos de código, puede terminar con puntos de código fuera del rango válido (por ejemplo, U+FFFFF (plano 15) XOR U+10000 (plano) 16) = U+1FFFFF (que sería el último carácter del plano 31), muy por encima del rango de los puntos de código existentes. Y también podría terminar de esta manera con los puntos de código reservados para los sustitutos (= no válidos).

Si sus cadenas solo contienen caracteres <128, 256, 512, 1024, 2048, 4096, 8192, 16384 o 32768, las cadenas XORed (en cuanto a las características) estarán en el mismo rango, y por lo tanto, ciertamente no contendrán ningún sustituto. En los primeros dos casos, también podría codificar su cadena como ASCII o Latin-1, respectivamente, y tener el mismo resultado XOR para los bytes. (Todavía puede terminar con caracteres de control, lo que puede ser un problema para usted).


Lo que finalmente estoy diciendo aquí : no espere que el resultado de encriptar Cadenas sea una cadena válida nuevamente; en su lugar, simplemente almacénelo y transmítalo como un byte[] (o una secuencia de bytes). (Y sí, conviértalo en UTF-8 antes de cifrar, y en UTF-8 después de descifrarlo).

Suponiendo (!) Que las cadenas tienen la misma longitud, ¿por qué no convertir las cadenas a matrices de bytes y luego XOR a los bytes? Las matrices de bytes resultantes también pueden tener diferentes longitudes dependiendo de su encoding (por ejemplo, UTF8 se ampliará a diferentes longitudes de bytes para diferentes caracteres).

Debe tener cuidado de especificar la encoding de caracteres para garantizar una conversión de cadena / byte consistente / confiable.

Este es el código que estoy usando:

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

la función abs es cuando las cuerdas no tienen la misma longitud, por lo que la longitud del resultado será la misma que la longitud mínima de las dos cuerdas a y 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(); } 

Esta solución es compatible con Android (la he probado y usado yo mismo). Gracias a @ user467257 cuya solución he adaptado.

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