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