]> jfr.im git - irc/rizon/acid.git/blob - acid/src/main/java/net/rizon/acid/util/CloakGenerator.java
f1ddca10f535eba0bfa84e00ca34ac5328f0a51d
[irc/rizon/acid.git] / acid / src / main / java / net / rizon / acid / util / CloakGenerator.java
1 package net.rizon.acid.util;
2
3 import java.net.Inet4Address;
4 import java.net.Inet6Address;
5 import java.net.InetAddress;
6 import java.net.UnknownHostException;
7 import java.security.MessageDigest;
8 import java.security.NoSuchAlgorithmException;
9
10 /**
11 * CloakGenerator that generates a cloaked host from an input IP or hostname.
12 *
13 * @author Shiz
14 */
15 public class CloakGenerator
16 {
17
18 protected String[] keys;
19 protected String hostPrefix;
20 protected MessageDigest digest;
21
22 public static boolean validateCloakKey(final String key)
23 {
24 boolean hasLowerCase = false;
25 boolean hasUpperCase = false;
26 boolean hasDigit = false;
27
28 for (int i = 0; i < key.length(); i++)
29 {
30 char c = key.charAt(i);
31 if (!hasLowerCase && Character.isLowerCase(c))
32 {
33 hasLowerCase = true;
34 }
35 if (!hasUpperCase && Character.isUpperCase(c))
36 {
37 hasUpperCase = true;
38 }
39 if (!hasDigit && Character.isDigit(c))
40 {
41 hasDigit = true;
42 }
43 if (hasLowerCase && hasUpperCase && hasDigit)
44 {
45 return true;
46 }
47 }
48
49 return false;
50 }
51
52 public CloakGenerator(String[] keys, String hostPrefix)
53 {
54 if (keys.length != 3)
55 {
56 throw new IllegalArgumentException("There must be 3 keys for the cloaking system.");
57 }
58
59 this.keys = keys;
60 this.hostPrefix = hostPrefix;
61
62 try
63 {
64 this.digest = MessageDigest.getInstance("MD5");
65 }
66 catch (NoSuchAlgorithmException ex)
67 {
68 throw new RuntimeException("Unable to load MD5 digest", ex);
69 }
70 }
71
72 /**
73 * * Cloak an arbitrary host. Will do detection to figure out what kind
74 * of host.
75 * @param host
76 * @return
77 */
78 public String cloak(final String host)
79 {
80 /* IPv6. */
81 if (host.contains(":"))
82 {
83 try
84 {
85 return this.cloak(Inet6Address.getByName(host));
86 }
87 catch (UnknownHostException e)
88 {
89 return null;
90 }
91 }
92 /* IPv4 or host. */
93 else if (host.contains("."))
94 {
95 for (char c : host.toCharArray())
96 {
97 if (c != '.' && !Character.isDigit(c)) {
98 /* Host. */
99 return this.cloakHost(host);
100 }
101 }
102 /* IPv4. */
103 try
104 {
105 return this.cloak(Inet4Address.getByName(host));
106 }
107 catch (UnknownHostException e)
108 {
109 return null;
110 }
111 /* Host */
112 }
113 else
114 {
115 return this.cloakHost(host);
116 }
117 }
118
119 /**
120 * Cloak an internet address.
121 */
122 private String cloak(InetAddress addr)
123 {
124 if (addr instanceof Inet4Address)
125 {
126 return this.cloakIPv4((Inet4Address) addr);
127 }
128 else if (addr instanceof Inet6Address)
129 {
130 return this.cloakIPv6((Inet6Address) addr);
131 }
132 return null;
133 }
134
135 /**
136 * Cloak an IPv4 address.
137 *
138 * Cloak format: A.B.C.D -> ALPHA.BETA.GAMMA.IP
139 *
140 * ALPHA: downmix(md5(md5(keys[1]:A.B.C.D:keys[2])keys[0]))
141 * BETA: downmix(md5(md5(keys[2]:A.B.C:keys[0])keys[1]))
142 * GAMMA: downmix(md5(md5(keys[0]:A.B:keys[1])keys[2]))
143 */
144 private String cloakIPv4(Inet4Address addr)
145 {
146 /* Gather IP pieces. */
147 byte[] bytePieces = addr.getAddress();
148 int[] pieces = new int[bytePieces.length];
149 for (int i = 0; i < bytePieces.length; i++)
150 {
151 pieces[i] = bytePieces[i] & 0xFF;
152 }
153
154 /* Generate alpha. */
155 String alphaInput = String.format("%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
156 String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
157
158 /* Generate beta. */
159 String betaInput = String.format("%d.%d.%d", pieces[0], pieces[1], pieces[2]);
160 String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
161
162 /* Generate gamma. */
163 String gammaInput = String.format("%d.%d", pieces[0], pieces[1]);
164 String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
165
166 return alpha + "." + beta + "." + gamma + ".IP";
167 }
168
169 /**
170 * Cloak an IPv6 address.
171 *
172 * Cloak format: a:b:c:d:e:f:g:h -> ALPHA:BETA:GAMMA:IP
173 *
174 * ALPHA: downmix(md5(md5(keys[1]:a:b:c:d:e:f:g:h:keys[2])keys[0]))
175 * BETA: downmix(md5(md5(keys[2]:a:b:c:d:e:f:g:keys[0])keys[1]))
176 * GAMMA: downmix(md5(md5(keys[0]:a:b:c:d:keys[1])keys[2]))
177 *
178 * @param addr
179 * @return
180 */
181 public String cloakIPv6(Inet6Address addr)
182 {
183 /* Gather IP pieces. */
184 byte[] bytePieces = addr.getAddress();
185 int[] pieces = new int[bytePieces.length / 2];
186 for (int i = 0; i < bytePieces.length; i += 2)
187 {
188 pieces[i / 2] = ((bytePieces[i] & 0xFF) << 8) | (bytePieces[i + 1] & 0xFF);
189 }
190
191 /* Generate alpha. */
192 String alphaInput = String.format("%x:%x:%x:%x:%x:%x:%x:%x:%x",
193 pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7], pieces[8]);
194 String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
195
196 /* Generate beta. */
197 String betaInput = String.format("%x:%x:%x:%x:%x:%x:%x",
198 pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6]);
199 String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
200
201 /* Generate gamma. */
202 String gammaInput = String.format("%x:%x:%x:%x", pieces[0], pieces[1], pieces[2], pieces[3]);
203 String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
204
205 return alpha + ":" + beta + ":" + gamma + ":IP";
206 }
207
208 /**
209 * Cloak a reverse DNS host.
210 *
211 * Cloak format: my.host.com -> PREFIX-ALPHA.host.com localhost ->
212 * PREFIX-ALPHA
213 *
214 * ALPHA: downmix(md5(md5(keys[0]:my.host.com:keys[1])keys[2]))
215 */
216 private String cloakHost(String host)
217 {
218 int dot = host.indexOf('.');
219 String alpha = this.stage(host, this.keys[0], this.keys[1], this.keys[2]);
220
221 while (dot != -1 && ((host.length() - 1) > dot) && !Character.isAlphabetic(host.charAt(dot + 1)))
222 {
223 host = host.substring(dot + 1);
224 dot = host.indexOf('.');
225 }
226
227 if (dot == -1 || dot == host.length() - 1)
228 {
229 return this.hostPrefix + alpha;
230 }
231
232 return this.hostPrefix + alpha + host.substring(dot);
233 }
234
235 /**
236 * Perform a downmix(md5(md5(key1:host:key2)key3)) stage.
237 * @param input
238 * @param key1
239 * @param key2
240 * @param key3
241 */
242 private String stage(String input, String key1, String key2, String key3)
243 {
244 String salted = String.format("%s:%s:%s", key1, input, key2);
245 byte[] firstMD5 = this.md5sum(salted);
246 byte[] secondMD5 = this.md5sum(firstMD5, key3.getBytes());
247 return this.downmix(secondMD5);
248 }
249
250 /**
251 * Mix the 128 bits of the MD5 hash into a single 32-bit value.
252 */
253 private String downmix(byte[] input)
254 {
255 byte[] res = new byte[4];
256 res[0] = (byte) (input[0] ^ input[1] ^ input[2] ^ input[3]);
257 res[1] = (byte) (input[4] ^ input[5] ^ input[6] ^ input[7]);
258 res[2] = (byte) (input[8] ^ input[9] ^ input[10] ^ input[11]);
259 res[3] = (byte) (input[12] ^ input[13] ^ input[14] ^ input[15]);
260
261 int r = (((int) res[0] & 0xFFFFFFFF) << 24)
262 + (((int) res[1] & 0xFFFFFFFF) << 16)
263 + (((int) res[2] & 0xFFFFFFFF) << 8)
264 + ((int) res[3] & 0xFFFFFFFF);
265 return String.format("%X", r);
266 }
267
268 private byte[] md5sum(String input)
269 {
270 return this.md5sum(input.getBytes());
271 }
272
273 private byte[] md5sum(byte[]... input)
274 {
275 this.digest.reset();
276 for (byte[] b : input)
277 {
278 this.digest.update(b);
279 }
280 return this.digest.digest();
281 }
282 }