Commit | Line | Data |
---|---|---|
52e0732e NR |
1 | package be.nikiroo.utils; |
2 | ||
3 | import java.io.IOException; | |
d20c8d77 NR |
4 | import java.io.InputStream; |
5 | import java.io.OutputStream; | |
9fb03c36 | 6 | import java.io.UnsupportedEncodingException; |
52e0732e | 7 | import java.security.InvalidKeyException; |
52e0732e NR |
8 | |
9 | import javax.crypto.BadPaddingException; | |
10 | import javax.crypto.Cipher; | |
d20c8d77 NR |
11 | import javax.crypto.CipherInputStream; |
12 | import javax.crypto.CipherOutputStream; | |
52e0732e | 13 | import javax.crypto.IllegalBlockSizeException; |
12784931 | 14 | import javax.crypto.spec.IvParameterSpec; |
52e0732e | 15 | import javax.crypto.spec.SecretKeySpec; |
9fb03c36 | 16 | import javax.net.ssl.SSLException; |
52e0732e | 17 | |
a6a73de3 NR |
18 | import be.nikiroo.utils.streams.Base64InputStream; |
19 | import be.nikiroo.utils.streams.Base64OutputStream; | |
20 | ||
52e0732e NR |
21 | /** |
22 | * Small utility class to do AES encryption/decryption. | |
23 | * <p> | |
0747c3c2 NR |
24 | * For the moment, it is multi-thread compatible, but beware: |
25 | * <ul> | |
26 | * <li>The encrypt/decrypt calls are serialized</li> | |
27 | * <li>The streams are independent and thus parallel</li> | |
28 | * </ul> | |
29 | * <p> | |
e61669f9 NR |
30 | * Do not assume it is actually secure, it is actually not. |
31 | * <p> | |
32 | * It just here to offer a more-or-less protected exchange of data because | |
33 | * anonymous and self-signed certificates backed SSL is against Google wishes | |
34 | * (so, don't even try, they own Internet). | |
52e0732e NR |
35 | * |
36 | * @author niki | |
37 | */ | |
38 | public class CryptUtils { | |
e61669f9 | 39 | static private final String AES_NAME = "AES/CFB128/NoPadding"; |
12784931 | 40 | |
52e0732e NR |
41 | private Cipher ecipher; |
42 | private Cipher dcipher; | |
3d730a54 | 43 | private byte[] bytes32; |
52e0732e NR |
44 | |
45 | /** | |
12784931 NR |
46 | * Small and lazy-easy way to initialize a 128 bits key with |
47 | * {@link CryptUtils}. | |
52e0732e NR |
48 | * <p> |
49 | * <b>Some</b> part of the key will be used to generate a 128 bits key and | |
50 | * initialize the {@link CryptUtils}; even NULL will generate something. | |
51 | * <p> | |
52 | * <b>This is most probably not secure. Do not use if you actually care | |
53 | * about security.</b> | |
54 | * | |
55 | * @param key | |
56 | * the {@link String} to use as a base for the key, can be NULL | |
57 | */ | |
58 | public CryptUtils(String key) { | |
59 | try { | |
0747c3c2 | 60 | init(key2key(key)); |
52e0732e NR |
61 | } catch (InvalidKeyException e) { |
62 | // We made sure that the key is correct, so nothing here | |
63 | e.printStackTrace(); | |
64 | } | |
65 | } | |
66 | ||
67 | /** | |
68 | * Create a new instance of {@link CryptUtils} with the given 128 bytes key. | |
69 | * <p> | |
70 | * The key <b>must</b> be exactly 128 bytes long. | |
71 | * | |
72 | * @param bytes32 | |
73 | * the 128 bits (32 bytes) of the key | |
74 | * | |
75 | * @throws InvalidKeyException | |
76 | * if the key is not an array of 128 bytes | |
77 | */ | |
78 | public CryptUtils(byte[] bytes32) throws InvalidKeyException { | |
79 | init(bytes32); | |
80 | } | |
81 | ||
d20c8d77 NR |
82 | /** |
83 | * Wrap the given {@link InputStream} so it is transparently encrypted by | |
84 | * the current {@link CryptUtils}. | |
85 | * | |
86 | * @param in | |
87 | * the {@link InputStream} to wrap | |
88 | * @return the auto-encode {@link InputStream} | |
89 | */ | |
12784931 | 90 | public InputStream encrypt(InputStream in) { |
0747c3c2 | 91 | Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); |
d20c8d77 NR |
92 | return new CipherInputStream(in, ecipher); |
93 | } | |
94 | ||
12784931 NR |
95 | /** |
96 | * Wrap the given {@link InputStream} so it is transparently encrypted by | |
97 | * the current {@link CryptUtils} and encoded in base64. | |
98 | * | |
99 | * @param in | |
100 | * the {@link InputStream} to wrap | |
12784931 NR |
101 | * |
102 | * @return the auto-encode {@link InputStream} | |
103 | * | |
104 | * @throws IOException | |
105 | * in case of I/O error | |
106 | */ | |
a6a73de3 NR |
107 | public InputStream encrypt64(InputStream in) throws IOException { |
108 | return new Base64InputStream(encrypt(in), true); | |
12784931 NR |
109 | } |
110 | ||
d20c8d77 NR |
111 | /** |
112 | * Wrap the given {@link OutputStream} so it is transparently encrypted by | |
113 | * the current {@link CryptUtils}. | |
114 | * | |
12784931 | 115 | * @param out |
d20c8d77 | 116 | * the {@link OutputStream} to wrap |
12784931 | 117 | * |
d20c8d77 NR |
118 | * @return the auto-encode {@link OutputStream} |
119 | */ | |
12784931 | 120 | public OutputStream encrypt(OutputStream out) { |
0747c3c2 | 121 | Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); |
d20c8d77 NR |
122 | return new CipherOutputStream(out, ecipher); |
123 | } | |
124 | ||
125 | /** | |
12784931 NR |
126 | * Wrap the given {@link OutputStream} so it is transparently encrypted by |
127 | * the current {@link CryptUtils} and encoded in base64. | |
128 | * | |
129 | * @param out | |
130 | * the {@link OutputStream} to wrap | |
12784931 NR |
131 | * |
132 | * @return the auto-encode {@link OutputStream} | |
133 | * | |
134 | * @throws IOException | |
135 | * in case of I/O error | |
136 | */ | |
a6a73de3 NR |
137 | public OutputStream encrypt64(OutputStream out) throws IOException { |
138 | return encrypt(new Base64OutputStream(out, true)); | |
12784931 NR |
139 | } |
140 | ||
141 | /** | |
142 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
d20c8d77 NR |
143 | * current {@link CryptUtils}. |
144 | * | |
145 | * @param in | |
146 | * the {@link InputStream} to wrap | |
12784931 | 147 | * |
d20c8d77 NR |
148 | * @return the auto-decode {@link InputStream} |
149 | */ | |
12784931 | 150 | public InputStream decrypt(InputStream in) { |
0747c3c2 | 151 | Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); |
d20c8d77 NR |
152 | return new CipherInputStream(in, dcipher); |
153 | } | |
154 | ||
155 | /** | |
12784931 NR |
156 | * Wrap the given {@link OutputStream} so it is transparently decoded by the |
157 | * current {@link CryptUtils} and decoded from base64. | |
158 | * | |
159 | * @param in | |
160 | * the {@link InputStream} to wrap | |
12784931 NR |
161 | * |
162 | * @return the auto-decode {@link InputStream} | |
163 | * | |
164 | * @throws IOException | |
165 | * in case of I/O error | |
166 | */ | |
a6a73de3 NR |
167 | public InputStream decrypt64(InputStream in) throws IOException { |
168 | return decrypt(new Base64InputStream(in, false)); | |
12784931 NR |
169 | } |
170 | ||
171 | /** | |
172 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
d20c8d77 NR |
173 | * current {@link CryptUtils}. |
174 | * | |
175 | * @param out | |
176 | * the {@link OutputStream} to wrap | |
177 | * @return the auto-decode {@link OutputStream} | |
178 | */ | |
12784931 | 179 | public OutputStream decrypt(OutputStream out) { |
0747c3c2 | 180 | Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); |
d20c8d77 NR |
181 | return new CipherOutputStream(out, dcipher); |
182 | } | |
183 | ||
12784931 NR |
184 | /** |
185 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
186 | * current {@link CryptUtils} and decoded from base64. | |
187 | * | |
188 | * @param out | |
189 | * the {@link OutputStream} to wrap | |
12784931 NR |
190 | * |
191 | * @return the auto-decode {@link OutputStream} | |
192 | * | |
193 | * @throws IOException | |
194 | * in case of I/O error | |
195 | */ | |
a6a73de3 NR |
196 | public OutputStream decrypt64(OutputStream out) throws IOException { |
197 | return new Base64OutputStream(decrypt(out), false); | |
12784931 NR |
198 | } |
199 | ||
52e0732e NR |
200 | /** |
201 | * This method required an array of 128 bytes. | |
202 | * | |
203 | * @param bytes32 | |
204 | * the array, which <b>must</b> be of 128 bits (32 bytes) | |
205 | * | |
206 | * @throws InvalidKeyException | |
207 | * if the key is not an array of 128 bits (32 bytes) | |
208 | */ | |
209 | private void init(byte[] bytes32) throws InvalidKeyException { | |
210 | if (bytes32 == null || bytes32.length != 32) { | |
211 | throw new InvalidKeyException( | |
212 | "The size of the key must be of 128 bits (32 bytes), it is: " | |
213 | + (bytes32 == null ? "null" : "" + bytes32.length) | |
214 | + " bytes"); | |
215 | } | |
216 | ||
3d730a54 NR |
217 | this.bytes32 = bytes32; |
218 | this.ecipher = newCipher(Cipher.ENCRYPT_MODE); | |
219 | this.dcipher = newCipher(Cipher.DECRYPT_MODE); | |
0747c3c2 | 220 | } |
12784931 | 221 | |
0747c3c2 NR |
222 | /** |
223 | * Create a new {@link Cipher}of the given mode (see | |
224 | * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}). | |
225 | * | |
226 | * @param mode | |
227 | * the mode ({@link Cipher#ENCRYPT_MODE} or | |
228 | * {@link Cipher#ENCRYPT_MODE}) | |
229 | * | |
230 | * @return the new {@link Cipher} | |
231 | */ | |
232 | private Cipher newCipher(int mode) { | |
52e0732e | 233 | try { |
3d730a54 NR |
234 | // bytes32 = 32 bytes, 32 > 16 |
235 | byte[] iv = new byte[16]; | |
236 | for (int i = 0; i < iv.length; i++) { | |
237 | iv[i] = bytes32[i]; | |
238 | } | |
12784931 | 239 | IvParameterSpec ivspec = new IvParameterSpec(iv); |
0747c3c2 | 240 | Cipher cipher = Cipher.getInstance(AES_NAME); |
3d730a54 | 241 | cipher.init(mode, new SecretKeySpec(bytes32, "AES"), ivspec); |
0747c3c2 | 242 | return cipher; |
e61669f9 | 243 | } catch (Exception e) { |
12784931 | 244 | e.printStackTrace(); |
e61669f9 NR |
245 | throw new RuntimeException( |
246 | "Cannot initialize encryption sub-system", e); | |
52e0732e NR |
247 | } |
248 | } | |
249 | ||
250 | /** | |
251 | * Encrypt the data. | |
252 | * | |
253 | * @param data | |
254 | * the data to encrypt | |
255 | * | |
256 | * @return the encrypted data | |
257 | * | |
9fb03c36 | 258 | * @throws SSLException |
52e0732e NR |
259 | * in case of I/O error (i.e., the data is not what you assumed |
260 | * it was) | |
261 | */ | |
9fb03c36 | 262 | public byte[] encrypt(byte[] data) throws SSLException { |
0747c3c2 NR |
263 | synchronized (ecipher) { |
264 | try { | |
265 | return ecipher.doFinal(data); | |
266 | } catch (IllegalBlockSizeException e) { | |
267 | throw new SSLException(e); | |
268 | } catch (BadPaddingException e) { | |
269 | throw new SSLException(e); | |
270 | } | |
52e0732e NR |
271 | } |
272 | } | |
273 | ||
274 | /** | |
275 | * Encrypt the data. | |
276 | * | |
277 | * @param data | |
278 | * the data to encrypt | |
279 | * | |
280 | * @return the encrypted data | |
281 | * | |
9fb03c36 | 282 | * @throws SSLException |
52e0732e NR |
283 | * in case of I/O error (i.e., the data is not what you assumed |
284 | * it was) | |
285 | */ | |
9fb03c36 | 286 | public byte[] encrypt(String data) throws SSLException { |
f8147a0e | 287 | return encrypt(StringUtils.getBytes(data)); |
52e0732e NR |
288 | } |
289 | ||
290 | /** | |
291 | * Encrypt the data, then encode it into Base64. | |
292 | * | |
293 | * @param data | |
294 | * the data to encrypt | |
295 | * @param zip | |
296 | * TRUE to also compress the data in GZIP format; remember that | |
297 | * compressed and not-compressed content are different; you need | |
298 | * to know which is which when decoding | |
299 | * | |
300 | * @return the encrypted data, encoded in Base64 | |
301 | * | |
9fb03c36 | 302 | * @throws SSLException |
52e0732e NR |
303 | * in case of I/O error (i.e., the data is not what you assumed |
304 | * it was) | |
305 | */ | |
a6a73de3 | 306 | public String encrypt64(String data) throws SSLException { |
f8147a0e | 307 | return encrypt64(StringUtils.getBytes(data)); |
52e0732e NR |
308 | } |
309 | ||
310 | /** | |
311 | * Encrypt the data, then encode it into Base64. | |
312 | * | |
313 | * @param data | |
314 | * the data to encrypt | |
52e0732e NR |
315 | * |
316 | * @return the encrypted data, encoded in Base64 | |
317 | * | |
9fb03c36 | 318 | * @throws SSLException |
52e0732e NR |
319 | * in case of I/O error (i.e., the data is not what you assumed |
320 | * it was) | |
321 | */ | |
a6a73de3 | 322 | public String encrypt64(byte[] data) throws SSLException { |
9fb03c36 | 323 | try { |
a6a73de3 | 324 | return StringUtils.base64(encrypt(data)); |
9fb03c36 NR |
325 | } catch (IOException e) { |
326 | // not exactly true, but we consider here that this error is a crypt | |
327 | // error, not a normal I/O error | |
328 | throw new SSLException(e); | |
329 | } | |
52e0732e NR |
330 | } |
331 | ||
332 | /** | |
333 | * Decode the data which is assumed to be encrypted with the same utilities. | |
334 | * | |
335 | * @param data | |
336 | * the encrypted data to decode | |
337 | * | |
338 | * @return the original, decoded data | |
339 | * | |
9fb03c36 | 340 | * @throws SSLException |
52e0732e NR |
341 | * in case of I/O error |
342 | */ | |
9fb03c36 | 343 | public byte[] decrypt(byte[] data) throws SSLException { |
0747c3c2 NR |
344 | synchronized (dcipher) { |
345 | try { | |
346 | return dcipher.doFinal(data); | |
347 | } catch (IllegalBlockSizeException e) { | |
348 | throw new SSLException(e); | |
349 | } catch (BadPaddingException e) { | |
350 | throw new SSLException(e); | |
351 | } | |
52e0732e NR |
352 | } |
353 | } | |
354 | ||
355 | /** | |
356 | * Decode the data which is assumed to be encrypted with the same utilities | |
ed3aa598 NR |
357 | * and to be a {@link String}. |
358 | * | |
359 | * @param data | |
360 | * the encrypted data to decode | |
361 | * | |
362 | * @return the original, decoded data,as a {@link String} | |
363 | * | |
364 | * @throws SSLException | |
365 | * in case of I/O error | |
366 | */ | |
367 | public String decrypts(byte[] data) throws SSLException { | |
368 | try { | |
369 | return new String(decrypt(data), "UTF-8"); | |
370 | } catch (UnsupportedEncodingException e) { | |
371 | // UTF-8 is required in all confirm JVMs | |
372 | e.printStackTrace(); | |
373 | return null; | |
374 | } | |
375 | } | |
376 | ||
377 | /** | |
378 | * Decode the data which is assumed to be encrypted with the same utilities | |
52e0732e NR |
379 | * and is a Base64 encoded value. |
380 | * | |
381 | * @param data | |
382 | * the encrypted data to decode in Base64 format | |
383 | * @param zip | |
384 | * TRUE to also uncompress the data from a GZIP format | |
385 | * automatically; if set to FALSE, zipped data can be returned | |
386 | * | |
387 | * @return the original, decoded data | |
388 | * | |
9fb03c36 | 389 | * @throws SSLException |
52e0732e NR |
390 | * in case of I/O error |
391 | */ | |
a6a73de3 | 392 | public byte[] decrypt64(String data) throws SSLException { |
9fb03c36 | 393 | try { |
a6a73de3 | 394 | return decrypt(StringUtils.unbase64(data)); |
9fb03c36 NR |
395 | } catch (IOException e) { |
396 | // not exactly true, but we consider here that this error is a crypt | |
397 | // error, not a normal I/O error | |
398 | throw new SSLException(e); | |
399 | } | |
52e0732e NR |
400 | } |
401 | ||
402 | /** | |
403 | * Decode the data which is assumed to be encrypted with the same utilities | |
404 | * and is a Base64 encoded value, then convert it into a String (this method | |
405 | * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}). | |
406 | * | |
407 | * @param data | |
408 | * the encrypted data to decode in Base64 format | |
409 | * @param zip | |
410 | * TRUE to also uncompress the data from a GZIP format | |
411 | * automatically; if set to FALSE, zipped data can be returned | |
412 | * | |
413 | * @return the original, decoded data | |
414 | * | |
9fb03c36 | 415 | * @throws SSLException |
52e0732e NR |
416 | * in case of I/O error |
417 | */ | |
a6a73de3 | 418 | public String decrypt64s(String data) throws SSLException { |
9fb03c36 | 419 | try { |
a6a73de3 | 420 | return new String(decrypt(StringUtils.unbase64(data)), "UTF-8"); |
9fb03c36 NR |
421 | } catch (UnsupportedEncodingException e) { |
422 | // UTF-8 is required in all confirm JVMs | |
423 | e.printStackTrace(); | |
424 | return null; | |
425 | } catch (IOException e) { | |
426 | // not exactly true, but we consider here that this error is a crypt | |
427 | // error, not a normal I/O error | |
428 | throw new SSLException(e); | |
429 | } | |
52e0732e NR |
430 | } |
431 | ||
432 | /** | |
433 | * This is probably <b>NOT</b> secure! | |
434 | * | |
435 | * @param input | |
436 | * some {@link String} input | |
437 | * | |
438 | * @return a 128 bits key computed from the given input | |
439 | */ | |
440 | static private byte[] key2key(String input) { | |
b1ed544b | 441 | return StringUtils.getMd5Hash("" + input).getBytes(); |
52e0732e NR |
442 | } |
443 | } |