From 9bf3268fb429006956f83a267a57949f8136c44c Mon Sep 17 00:00:00 2001 From: Sn0wStorm Date: Wed, 29 Jun 2016 22:44:19 +0200 Subject: [PATCH] Added XOR Scrambler for Brew data Very fast obfuscator for brew data so that a client cant easily read it --- src/com/dre/brewery/Brew.java | 44 ++++-- src/com/dre/brewery/P.java | 133 ++++++++++++++++++ src/com/dre/brewery/filedata/DataSave.java | 3 + src/com/dre/brewery/lore/SeedInputStream.java | 94 +++++++++++++ .../dre/brewery/lore/XORScrambleStream.java | 67 +++++++++ .../dre/brewery/lore/XORUnscrambleStream.java | 100 +++++++++++++ 6 files changed, 430 insertions(+), 11 deletions(-) create mode 100644 src/com/dre/brewery/lore/SeedInputStream.java create mode 100644 src/com/dre/brewery/lore/XORScrambleStream.java create mode 100644 src/com/dre/brewery/lore/XORUnscrambleStream.java diff --git a/src/com/dre/brewery/Brew.java b/src/com/dre/brewery/Brew.java index df16169..6964fe1 100644 --- a/src/com/dre/brewery/Brew.java +++ b/src/com/dre/brewery/Brew.java @@ -1,10 +1,7 @@ package com.dre.brewery; import org.bukkit.Color; -import com.dre.brewery.lore.Base91DecoderStream; -import com.dre.brewery.lore.Base91EncoderStream; -import com.dre.brewery.lore.LoreLoadStream; -import com.dre.brewery.lore.LoreSaveStream; +import com.dre.brewery.lore.*; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.BrewerInventory; @@ -20,6 +17,7 @@ import org.bukkit.potion.PotionType; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -28,7 +26,7 @@ import java.util.Map; public class Brew { // represents the liquid in the brewed Potions - + private static long saveSeed; public static Map legacyPotions = new HashMap<>(); public static long installTime = System.currentTimeMillis(); // plugin install time in millis after epoch public static Boolean colorInBarrels; // color the Lore while in Barrels @@ -732,18 +730,26 @@ public class Brew { } catch (IllegalArgumentException ignored) { return null; } - try (DataInputStream in = new DataInputStream(new Base91DecoderStream(loreStream))) { + XORUnscrambleStream unscrambler = new XORUnscrambleStream(new Base91DecoderStream(loreStream), saveSeed); + try (DataInputStream in = new DataInputStream(unscrambler)) { + boolean parityFailed = false; if (in.readByte() != 86) { - P.p.errorLog("parity check failed on Brew while loading, trying to load anyways!"); + P.p.errorLog("Parity check failed on Brew while loading, trying to load anyways!"); + parityFailed = true; } Brew brew = new Brew(); byte ver = in.readByte(); switch (ver) { case 1: + unscrambler.start(); brew.loadFromStream(in); break; default: - P.p.errorLog("Brew has data stored in v" + ver + " this version supports up to v1"); + if (parityFailed) { + P.p.errorLog("Failed to load Brew. Maybe something corrupted the Lore of the Item?"); + } else { + P.p.errorLog("Brew has data stored in v" + ver + " this Plugin version supports up to v1"); + } return null; } return brew; @@ -779,7 +785,11 @@ public class Brew { // Save brew data into meta/lore public void save(ItemMeta meta) { - try (DataOutputStream out = new DataOutputStream(new Base91EncoderStream(new LoreSaveStream(meta, 0)))) { + XORScrambleStream scrambler = new XORScrambleStream(new Base91EncoderStream(new LoreSaveStream(meta, 0)), saveSeed); + try (DataOutputStream out = new DataOutputStream(scrambler)) { + out.writeByte(86); // Parity/sanity + out.writeByte(1); // Version + scrambler.start(); saveToStream(out); } catch (IOException e) { P.p.errorLog("IO Error while saving Brew"); @@ -801,8 +811,6 @@ public class Brew { } public void saveToStream(DataOutputStream out) throws IOException { - out.writeByte(86); // Parity/sanity - out.writeByte(1); // Version if (quality > 10) { quality = 10; } @@ -831,6 +839,20 @@ public class Brew { ingredients.save(out); } + public static void writeSeed(ConfigurationSection section) { + section.set("seed", saveSeed); + } + + public static void loadSeed(ConfigurationSection section) { + if (section.contains("seed")) { + saveSeed = section.getLong("seed"); + } else { + while (saveSeed == 0) { + saveSeed = new SecureRandom().nextLong(); + } + } + } + // Load potion data from data file for backwards compatibility public static void loadLegacy(BIngredients ingredients, int uid, int quality, byte distillRuns, float ageTime, float wood, String recipe, boolean unlabeled, boolean persistent, boolean stat) { Brew brew = new Brew(ingredients, quality, distillRuns, ageTime, wood, recipe, unlabeled, stat); diff --git a/src/com/dre/brewery/P.java b/src/com/dre/brewery/P.java index e37b855..c5e2e82 100644 --- a/src/com/dre/brewery/P.java +++ b/src/com/dre/brewery/P.java @@ -86,6 +86,136 @@ public class P extends JavaPlugin { //P.p.log("§" + (use1_9 ? "a":"c") + "1.9 " + "§" + (use1_11 ? "a":"c") + "1.11 " + "§" + (use1_13 ? "a":"c") + "1.13 " + "§" + (use1_14 ? "a":"c") + "1.14"); + /*long master = new SecureRandom().nextLong(); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + XORScrambleStream scramble = new XORScrambleStream(new Base91EncoderStream(byteStream), master); + DataOutputStream data = new DataOutputStream(scramble); + DataInputStream dataIn = null; + try { + scramble.start(); + data.writeLong(12345L); + scramble.stop(); + data.writeInt(1); + data.writeInt(1); + scramble.start(); + data.writeDouble(0.55555D); + data.writeInt(234323); + //data.writeUTF("Hallo Peter"); + data.writeLong(5419L); // Skip + data.writeDouble(0.55555D); + + data.close(); + + XORUnscrambleStream unscramble = new XORUnscrambleStream(new Base91DecoderStream(new ByteArrayInputStream(byteStream.toByteArray())), master); + dataIn = new DataInputStream(unscramble); + unscramble.start(); + P.p.log(dataIn.readLong() + ""); + unscramble.stop(); + P.p.log(dataIn.readInt() + ""); + P.p.log(dataIn.readInt() + ""); + unscramble.start(); + P.p.log(dataIn.readDouble() + ""); + dataIn.mark(1000); + P.p.log(dataIn.readInt() + ""); + //P.p.log(dataIn.readUTF()); + dataIn.skip(8); + P.p.log(dataIn.readDouble() + ""); + P.p.log("reset"); + dataIn.reset(); + P.p.log(dataIn.readInt() + ""); + //P.p.log(dataIn.readUTF()); + dataIn.skip(8); + P.p.log(dataIn.readDouble() + ""); + + dataIn.close(); + + *//*for (int i = 0; i < 10; i++) { + byteStream = new ByteArrayOutputStream(); + scramble = new XORScrambleStream(new Base91EncoderStream(byteStream)); + data = new DataOutputStream(scramble); + data.writeInt(i); + scramble.start(); + data.writeLong(12345L); + data.writeLong(12345L); + scramble.stop(); + data.writeInt(1); + data.writeInt(1); + scramble.start(); + data.writeInt(234323); + data.writeDouble(0.55555D); + + P.p.log(byteStream.toString()); + data.close(); + }*//* + + + long time = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + unscramble = new XORUnscrambleStream(new Base91DecoderStream(new ByteArrayInputStream(byteStream.toByteArray())), master); + dataIn = new DataInputStream(unscramble); + unscramble.start(); + dataIn.readLong(); + unscramble.stop(); + dataIn.readInt(); + dataIn.readInt(); + unscramble.start(); + dataIn.readDouble(); + dataIn.mark(1000); + dataIn.readInt(); + //dataIn.readUTF(); + dataIn.skip(8); + dataIn.readDouble(); + dataIn.reset(); + dataIn.readInt(); + //dataIn.readUTF(); + dataIn.skip(8); + dataIn.readDouble(); + + dataIn.close(); + } + long time2 = System.currentTimeMillis(); + + for (int i = 0; i < 100000; i++) { + unscramble = new XORUnscrambleStream(new ByteArrayInputStream(byteStream.toByteArray()), master); + dataIn = new DataInputStream(unscramble); + unscramble.start(); + dataIn.skip(2); + dataIn.readLong(); + unscramble.stop(); + dataIn.readInt(); + dataIn.readInt(); + unscramble.start(); + dataIn.readDouble(); + dataIn.mark(1000); + dataIn.readInt(); + //dataIn.readUTF(); + dataIn.skip(8); + dataIn.readDouble(); + dataIn.reset(); + dataIn.readInt(); + //dataIn.readUTF(); + dataIn.skip(8); + dataIn.readDouble(); + + dataIn.close(); + } + long time3 = System.currentTimeMillis(); + + P.p.log("Time with base91: " + (time2 - time)); + P.p.log("Time without base91: " + (time3 - time2)); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + data.close(); + if (dataIn != null) { + dataIn.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + }*/ /*try { ItemMeta meta = new ItemStack(Material.POTION).getItemMeta(); @@ -577,8 +707,11 @@ public class P extends JavaPlugin { FileConfiguration data = YamlConfiguration.loadConfiguration(file); Brew.installTime = data.getLong("installTime", System.currentTimeMillis()); + MCBarrel.mcBarrelTime = data.getLong("MCBarrelTime", 0); + Brew.loadSeed(data); + // Check if data is the newest version String version = data.getString("Version", null); if (version != null) { diff --git a/src/com/dre/brewery/filedata/DataSave.java b/src/com/dre/brewery/filedata/DataSave.java index 6f1a3a0..f3d9a6a 100644 --- a/src/com/dre/brewery/filedata/DataSave.java +++ b/src/com/dre/brewery/filedata/DataSave.java @@ -62,8 +62,11 @@ public class DataSave extends BukkitRunnable { FileConfiguration configFile = new YamlConfiguration(); configFile.set("installTime", Brew.installTime); + configFile.set("MCBarrelTime", MCBarrel.mcBarrelTime); + Brew.writeSeed(configFile); + if (!Brew.legacyPotions.isEmpty()) { Brew.save(configFile.createSection("Brew")); } diff --git a/src/com/dre/brewery/lore/SeedInputStream.java b/src/com/dre/brewery/lore/SeedInputStream.java new file mode 100644 index 0000000..c293297 --- /dev/null +++ b/src/com/dre/brewery/lore/SeedInputStream.java @@ -0,0 +1,94 @@ +package com.dre.brewery.lore; + +import java.io.InputStream; +import java.util.Arrays; + +public class SeedInputStream extends InputStream { + // From java.util.Random + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + private long seed; + private byte[] buf = new byte[4]; + private byte reader = 4; + private long markSeed; + private byte[] markbuf; + + public SeedInputStream(long seed) { + this.seed = (seed ^ multiplier) & mask; + } + + private void calcSeed() { + seed = (seed * multiplier + addend) & mask; + } + + private void genNext() { + calcSeed(); + int next = (int)(seed >>> 16); + buf[0] = (byte) (next >> 24); + buf[1] = (byte) (next >> 16); + buf[2] = (byte) (next >> 8); + buf[3] = (byte) next; + reader = 0; + } + + @Override + public int read(byte[] b, int off, int len) { + for (int i = off; i < len; i++) { + if (reader >= 4) { + genNext(); + } + b[i] = buf[reader++]; + } + return len; + } + + @Override + public int read() { + if (reader == 4) { + genNext(); + } + return buf[reader++]; + } + + @Override + public long skip(long toSkip) { + long n = toSkip; + while (n > 0) { + if (reader < 4) { + reader++; + n--; + } else if (n >= 4) { + calcSeed(); + n -= 4; + } else { + genNext(); + } + } + return toSkip; + } + + @Override + public void close() { + buf = null; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public synchronized void mark(int readlimit) { + markbuf = new byte[] {buf[0], buf[1], buf[2], buf[3], reader}; + markSeed = seed; + } + + @Override + public synchronized void reset() { + seed = markSeed; + buf = Arrays.copyOfRange(markbuf, 0, 4); + reader = markbuf[4]; + } +} diff --git a/src/com/dre/brewery/lore/XORScrambleStream.java b/src/com/dre/brewery/lore/XORScrambleStream.java new file mode 100644 index 0000000..da669c5 --- /dev/null +++ b/src/com/dre/brewery/lore/XORScrambleStream.java @@ -0,0 +1,67 @@ +package com.dre.brewery.lore; + + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +public class XORScrambleStream extends FilterOutputStream { + + private final long seed; + private SeedInputStream xorStream; + private boolean running; + + public XORScrambleStream(OutputStream out, long seed) { + super(out); + this.seed = seed; + } + + public void start() throws IOException { + running = true; + if (xorStream == null) { + short id = 0; + while (id == 0) { + id = (short) new Random().nextInt(); + } + xorStream = new SeedInputStream(seed ^ id); + out.write((byte) (id >> 8)); + out.write((byte) id); + } + } + + public void stop() { + running = false; + } + + @Override + public void write(int b) throws IOException { + if (!running) { + out.write(b); + return; + } + out.write(b ^ xorStream.read()); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (!running) { + out.write(b, off, len); + return; + } + byte[] xored = new byte[len]; + xorStream.read(xored); + int j = off; + for (int i = 0; i < len; i++) { + xored[i] ^= b[j++]; + } + out.write(xored); + } + + @Override + public void close() throws IOException { + running = false; + xorStream = null; + super.close(); + } +} diff --git a/src/com/dre/brewery/lore/XORUnscrambleStream.java b/src/com/dre/brewery/lore/XORUnscrambleStream.java new file mode 100644 index 0000000..44e49a6 --- /dev/null +++ b/src/com/dre/brewery/lore/XORUnscrambleStream.java @@ -0,0 +1,100 @@ +package com.dre.brewery.lore; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class XORUnscrambleStream extends FilterInputStream { + + private final long seed; + private SeedInputStream xorStream; + private boolean running; + private boolean markRunning; + private boolean markxor; + + public XORUnscrambleStream(InputStream in, long seed) { + super(in); + this.seed = seed; + } + + public void start() throws IOException { + running = true; + if (xorStream == null) { + short id = (short) (in.read() << 8 | in.read()); + if (id == 0) { + running = false; + return; + } + xorStream = new SeedInputStream(seed ^ id); + } + } + + public void stop() { + running = false; + } + + @Override + public int read() throws IOException { + if (!running) { + return in.read(); + } + return (in.read() ^ xorStream.read()) & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!running) { + return in.read(b, off, len); + } + len = in.read(b, off, len); + for (int i = off; i < len + off; i++) { + b[i] ^= xorStream.read(); + } + return len; + } + + @Override + public long skip(long n) throws IOException { + long skipped = in.skip(n); + if (running && skipped > 0) { + xorStream.skip(skipped); + } + return skipped; + } + + @Override + public void close() throws IOException { + if (xorStream != null) { + xorStream.close(); + xorStream = null; + } + running = false; + super.close(); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public synchronized void reset() throws IOException { + in.reset(); + if (markxor) { + xorStream.reset(); + } else { + xorStream = null; + } + running = markRunning; + } + + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + if (xorStream != null) { + xorStream.mark(readlimit); + markxor = true; + } + markRunning = running; + } +}