如何在Java中对两个字符串执行位异或操作。
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", "");
}
}
进行Base64编码是因为对一个字符串的字节进行异或操作可能不会得到有效的字节。
private String base64Encode(byte[] bytes) {
return Base64.encodeToString(bytes,Base64.DEFAULT).replaceAll("\\s", "");
}
- JohnCString 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();
回应 @user467257 的评论:
如果你的输入/输出是utf-8,并且你对“a”和“æ”进行异或运算,你将得到一个无效的utf-8字符串,其中只包含一个字符(十进制135,一个连续字符)。
被异或的是char值而不是字节值,这会产生一个可以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")));
}
打印
135 UTF-8 encoded is [-62, -121]
char
和byte
这两个不同的概念。我已经更新了我的回答并针对你的评论进行了回复。 - Peter Lawrey'\u11b0' ^ '\uc810'
。如果您在具有不成对代理项的字符串上使用getBytes
,它将为UTF-8生成?
,并为UTF-16生成REPLACEMENT CHARACTER \ufffd
。 - nhahtdh注意:
char
是 Java 中对应UTF-16编码单元的数据类型,有时候需要两个连续的char
(即所谓的代理对)表示一个真正的Unicode字符(代码点)。
对于两个有效的UTF-16序列(比如Java字符串逐个char
进行XOR运算或将其编码为UTF-16后逐个字节进行XOR运算),结果不一定是另一个有效的UTF-16字符串 - 结果可能会包含未配对的代理项(surrogates)。 (它仍然可以作为完全可用的Java字符串使用,只是与代码点相关的方法可能会混淆,并且将其转换为其他编码以进行输出等方法也可能会产生问题。)
如果您首先将字符串转换为UTF-8,然后再进行XOR运算,则您很可能会得到一个无效的UTF-8字节序列,如果您的字符串不是纯ASCII字符串,则会出现此情况。
即使您尝试正确地迭代两个字符串的代码点并尝试XOR这些代码点,您最终也可能得到超出有效范围的代码点(例如,U+FFFFF
(第15平面)XOR U+10000
(第16平面)=U+1FFFFF
(可能是第31平面的最后一个字符),超出现有代码点的范围,您也可能以这种方式得到代理项保留的代码点(即无效代码点)。
如果字符串仅包含< 128, 256, 512, 1024, 2048, 4096, 8192, 16384, or 32768
的字符,则按字符运算XOR的字符串将在相同的范围内,因此肯定不包含任何代理项。 在前两种情况下,您也可以分别将字符串编码为ASCII或Latin-1并获得相同的XOR字节结果。(但仍可能包含控制字符,这可能是个问题。)
我最终想说的是:不要期望加密字符串后的结果再次成为有效字符串——相反,只需将其存储和传输为byte[]
(或者字节流)。(是的,在加密之前转换为UTF-8,在解密之后从UTF-8转换回来)。
这个解决方案适用于Android(我已经测试并使用过它)。感谢@user467257,我从他的解决方案中进行了适应。
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));
}
}
base64Encode()
没有被使用过,最好使用 Base64.NO_WRAP
进行编码以避免出现换行符。 - gmk57这是我正在使用的代码:
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;
}
"$".getBytes()
,它可能是1个字节,"£"可能是2个字节,"€"可能是3个字节。(它们都是UTF-8编码) - Peter Lawreyint
表示的char
将在UTF-8中用多个字节表示吗? - artaxerxeabs函数用于在字符串长度不相同时,其结果长度将等于字符串a和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();
}