Java で 2 つの文字列に対してビット単位の XOR 演算を行う方法。
7 に答える
次のようなものが必要です。
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 すると、文字列の有効なバイトが返されない可能性があるため、base64 エンコードが行われます。
注: これは、0x8000 未満の低い文字に対してのみ機能します。これは、すべての ASCII 文字に対して機能します。
charAt() ごとに XOR を実行して、新しい文字列を作成します。お気に入り
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]
注意を払う:
Javachar
は UTF-16 コード単位に対応し、場合によっては、1 つの実際の Unicode 文字 (コードポイント) に対して 2 つの連続しchar
た s (いわゆるサロゲート ペア) が必要になります。
2 つの有効な UTF-16 シーケンス (つまり、Java Strings char
by char
、または UTF-16 にエンコードした後のバイト単位) を XOR しても、別の有効な UTF-16 文字列が得られるとは限りません。(それでも完全に使用可能な Java String ですが、コードポイントに関係するメソッドだけが混乱する可能性があり、出力などのために他のエンコーディングに変換するメソッドが混乱する可能性があります。)
最初に文字列を UTF-8 に変換してから、これらのバイトを XOR した場合も同様です。ここで、文字列がまだ両方とも純粋な ASCII 文字列でない場合、有効な UTF-8 ではないバイト シーケンスになる可能性が非常に高くなります。
コードポイントごとに 2 つの文字列を反復処理し、コードポイントを XOR しようとしても、有効な範囲外のコードポイントになる可能性があります (たとえば、U+FFFFF
(plane 15) XOR U+10000
(plane 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 は文字ごとに異なるバイト長に展開されます)。
一貫性のある信頼性の高い文字列/バイト変換を保証するために、文字エンコーディングを慎重に指定する必要があります。
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();
}