Javaで2つの文字列に対してビット単位でXOR操作を行う方法。
次のようなものが必要です:
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", "");
}
}
文字列のバイトのxor'ingが文字列に有効なバイトを返さない場合があるため、base64エンコードが行われます。
注:これは、低い文字、つまり0x8000以下でのみ機能します。これは、すべてのASCII文字で機能します。
XOR各charAt()を実行して新しい文字列を作成します。
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();
@ user467257のコメントに応えて
入力/出力がutf-8で、「a」と「æ」をxorすると、1文字(10進数の135、継続文字)で構成される無効なutf-8文字列が残ります。
Xorされるのは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]
注意:
Java char
はUTF-16コード単位に対応し、場合によっては2つの連続したchar
s(いわゆるサロゲートペア)は、1つの実際のUnicode文字(コードポイント)に必要です。
2つの有効なUTF-16シーケンス(つまり、Java Strings char
by char
、またはUTF-16へのエンコード後のバイト単位)のXORを実行しても、必ずしも別の有効なUTF-16文字列-結果として、ペアになっていないサロゲートが存在する場合があります(これは、完全に使用可能なJava文字列、コードポイントに関連するメソッドだけが混乱する可能性があり、出力などのために他のエンコーディングに変換します。)
最初に文字列をUTF-8に変換し、次にXORこれらのバイト-ここでおそらく)がバイトシーケンスではなく、有効なUTF-8(文字列が両方とも純粋なASCII文字列でなかった場合)。
正しく実行しようとして、コードポイントで2つの文字列を反復処理し、XOR=コードポイントを試みると、有効範囲外のコードポイント(たとえば、U+FFFFF
(プレーン15)XOR U+10000
(プレーン16)= U+1FFFFF
(プレーン31の最後の文字)、既存のコードポイントの範囲をはるかに超えています。また、サロゲート用に予約されたコードポイント(=無効なコードポイント)でこのようになる可能性があります。
文字列に含まれる文字が128、256、512、1024、2048、4096、8192、16384、または32768未満の文字のみの場合、(文字単位の)XORされた文字列は同じ範囲にあるため、サロゲートは含まれません。最初の2つのケースでは、文字列をASCIIまたはLatin-1としてそれぞれエンコードし、バイトに対して同じXOR結果を得ることができます。あなたにとって問題かもしれません。)
私が最後にここで言っていること:文字列を暗号化した結果が再び有効な文字列になることを期待しないでください-代わりに、単にそれをbyte[]
(またはバイトのストリーム)。 (そして、はい、暗号化する前にUTF-8に変換し、復号化した後にUTF-8から変換します)。
これは私が使用しているコードです:
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;
}
文字列の長さが等しい(!)と仮定して、なぜ 文字列をバイト配列に変換する で、その後XORバイト。結果のバイト配列は異なる長さになる可能性があります。エンコードにも依存します(たとえば、UTF8は異なる文字に対して異なるバイト長に拡張されます)。
一貫性/信頼性のある文字列/バイト変換を保証するために、文字エンコーディングを指定するよう注意する必要があります。
このソリューションは、Android(私は自分でテストして使用した)と互換性があります。
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));
}
}
abs関数は、文字列が同じ長さではないため、結果の長さが2つの文字列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();
}