+package net.rizon.acid.util;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * CloakGenerator that generates a cloaked host from an input IP or hostname.
+ *
+ * @author Shiz
+ */
+public class CloakGenerator
+{
+
+ protected String[] keys;
+ protected String hostPrefix;
+ protected MessageDigest digest;
+
+ public static boolean validateCloakKey(final String key)
+ {
+ boolean hasLowerCase = false;
+ boolean hasUpperCase = false;
+ boolean hasDigit = false;
+
+ for (int i = 0; i < key.length(); i++)
+ {
+ char c = key.charAt(i);
+ if (!hasLowerCase && Character.isLowerCase(c))
+ {
+ hasLowerCase = true;
+ }
+ if (!hasUpperCase && Character.isUpperCase(c))
+ {
+ hasUpperCase = true;
+ }
+ if (!hasDigit && Character.isDigit(c))
+ {
+ hasDigit = true;
+ }
+ if (hasLowerCase && hasUpperCase && hasDigit)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public CloakGenerator(String[] keys, String hostPrefix) throws NoSuchAlgorithmException
+ {
+ this.keys = keys;
+ this.hostPrefix = hostPrefix;
+ this.digest = MessageDigest.getInstance("MD5");
+ }
+
+ /**
+ * * Cloak an arbitrary host. Will do detection to figure out what kind
+ * of host.
+ */
+ public String cloak(String host)
+ {
+ /* IPv6. */
+ if (host.contains(":"))
+ {
+ /* Plexus' cloaking code has an IPv6 parsing bug where it stops scanning at the first ::. */
+ int cutoff = host.indexOf("::");
+ if (cutoff != -1)
+ {
+ host = host.substring(0, cutoff + 2);
+ }
+
+ try
+ {
+ return this.cloak(Inet6Address.getByName(host));
+ }
+ catch (UnknownHostException e)
+ {
+ return null;
+ }
+ /* IPv4 or host. */
+ }
+ else if (host.contains("."))
+ {
+ for (char c : host.toCharArray())
+ {
+ if (c != '.' && !Character.isDigit(c)) /* Host. */ {
+ return this.cloakHost(host);
+ }
+ }
+ /* IPv4. */
+ try
+ {
+ return this.cloak(Inet4Address.getByName(host));
+ }
+ catch (UnknownHostException e)
+ {
+ return null;
+ }
+ /* Host */
+ }
+ else
+ {
+ return this.cloakHost(host);
+ }
+ }
+
+ /**
+ * Cloak an internet address.
+ */
+ public String cloak(InetAddress addr)
+ {
+ if (addr instanceof Inet4Address)
+ {
+ return this.cloakIPv4((Inet4Address) addr);
+ }
+ else if (addr instanceof Inet6Address)
+ {
+ return this.cloakIPv6((Inet6Address) addr);
+ }
+ return null;
+ }
+
+ /**
+ * Cloak an IPv4 address.
+ *
+ * Cloak format: A.B.C.D -> ALPHA.BETA.GAMMA.IP
+ *
+ * ALPHA: downmix(md5(md5(keys[1]:A.B.C.D:keys[2])keys[0])) BETA:
+ * downmix(md5(md5(keys[2]:A.B.C:keys[0])keys[1])) GAMMA:
+ * downmix(md5(md5(keys[0]:A.B:keys[1])keys[2]))
+ */
+ public String cloakIPv4(Inet4Address addr)
+ {
+ /* Gather IP pieces. */
+ byte[] bytePieces = addr.getAddress();
+ int[] pieces = new int[bytePieces.length];
+ for (int i = 0; i < bytePieces.length; i++)
+ {
+ pieces[i] = bytePieces[i] & 0xFF;
+ }
+
+ /* Generate alpha. */
+ String alphaInput = String.format("%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
+ String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
+
+ /* Generate beta. */
+ String betaInput = String.format("%d.%d.%d", pieces[0], pieces[1], pieces[2]);
+ String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
+
+ /* Generate gamma. */
+ String gammaInput = String.format("%d.%d", pieces[0], pieces[1]);
+ String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
+
+ return alpha + "." + beta + "." + gamma + ".IP";
+ }
+
+ /**
+ * Cloak an IPv6 address.
+ *
+ * Cloak format: a:b:c:d:e:f:g:h -> ALPHA:BETA:GAMMA:IP
+ *
+ * ALPHA: downmix(md5(md5(keys[1]:a:b:c:d:e:f:g:h:keys[2])keys[0]))
+ * BETA: downmix(md5(md5(keys[2]:a:b:c:d:e:f:g:keys[0])keys[1])) GAMMA:
+ * downmix(md5(md5(keys[0]:a:b:c:d:keys[1])keys[2]))
+ */
+ public String cloakIPv6(Inet6Address addr)
+ {
+ /* Gather IP pieces. */
+ byte[] bytePieces = addr.getAddress();
+ int[] pieces = new int[bytePieces.length / 2];
+ System.out.println("Byte length: " + bytePieces.length);
+ for (int i = 0; i < bytePieces.length; i += 2)
+ {
+ pieces[i / 2] = ((bytePieces[i] & 0xFF) << 8) | (bytePieces[i + 1] & 0xFF);
+ }
+
+ /* Generate alpha. */
+ String alphaInput = String.format("%x:%x:%x:%x:%x:%x:%x:%x",
+ pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7]);
+ String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
+
+ /* Generate beta. */
+ String betaInput = String.format("%x:%x:%x:%x:%x:%x:%x",
+ pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6]);
+ String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
+
+ /* Generate gamma. */
+ String gammaInput = String.format("%x:%x:%x:%x", pieces[0], pieces[1], pieces[2], pieces[3]);
+ String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
+
+ return alpha + ":" + beta + ":" + gamma + ":IP";
+ }
+
+ /**
+ * Cloak a reverse DNS host.
+ *
+ * Cloak format: my.host.com -> PREFIX-ALPHA.host.com localhost ->
+ * PREFIX-ALPHA
+ *
+ * ALPHA: downmix(md5(md5(keys[0]:my.host.com:keys[1])keys[2]))
+ */
+ public String cloakHost(String host)
+ {
+ int dot = host.indexOf('.');
+ String alpha = this.stage(host, this.keys[0], this.keys[1], this.keys[2]);
+
+ if (dot == -1 || dot == host.length() - 1)
+ {
+ return this.hostPrefix + alpha;
+ }
+ else
+ {
+ return this.hostPrefix + alpha + host.substring(dot);
+ }
+ }
+
+ /**
+ * Perform a downmix(md5(md5(key1:host:key2)key3)) stage.
+ */
+ protected String stage(String input, String key1, String key2, String key3)
+ {
+ String salted = String.format("%s:%s:%s", key1, input, key2);
+ byte[] firstMD5 = this.md5sum(salted);
+ byte[] secondMD5 = this.md5sum(firstMD5, key3.getBytes());
+ return this.downmix(secondMD5);
+ }
+
+ /**
+ * Mix the 128 bits of the MD5 hash into a single 32-bit value.
+ */
+ protected String downmix(byte[] input)
+ {
+ byte[] res = new byte[4];
+ res[0] = (byte) (input[0] ^ input[1] ^ input[2] ^ input[3]);
+ res[1] = (byte) (input[4] ^ input[5] ^ input[6] ^ input[7]);
+ res[2] = (byte) (input[8] ^ input[9] ^ input[10] ^ input[11]);
+ res[3] = (byte) (input[12] ^ input[13] ^ input[14] ^ input[15]);
+
+ int r = (((int) res[0] & 0xFFFFFFFF) << 24)
+ + (((int) res[1] & 0xFFFFFFFF) << 16)
+ + (((int) res[2] & 0xFFFFFFFF) << 8)
+ + ((int) res[3] & 0xFFFFFFFF);
+ return String.format("%X", r);
+ }
+
+ protected byte[] md5sum(String input)
+ {
+ return this.md5sum(input.getBytes());
+ }
+
+ protected byte[] md5sum(byte[]... input)
+ {
+ this.digest.reset();
+ for (byte[] b : input)
+ {
+ this.digest.update(b);
+ }
+ return this.digest.digest();
+ }
+}