bugfixes
[nikiroo-utils.git] / src / be / nikiroo / utils / CryptUtils.java
CommitLineData
52e0732e
NR
1package be.nikiroo.utils;
2
3import java.io.IOException;
d20c8d77
NR
4import java.io.InputStream;
5import java.io.OutputStream;
9fb03c36 6import java.io.UnsupportedEncodingException;
12784931 7import java.security.InvalidAlgorithmParameterException;
52e0732e
NR
8import java.security.InvalidKeyException;
9import java.security.NoSuchAlgorithmException;
10
11import javax.crypto.BadPaddingException;
12import javax.crypto.Cipher;
d20c8d77
NR
13import javax.crypto.CipherInputStream;
14import javax.crypto.CipherOutputStream;
52e0732e
NR
15import javax.crypto.IllegalBlockSizeException;
16import javax.crypto.NoSuchPaddingException;
17import javax.crypto.SecretKey;
12784931 18import javax.crypto.spec.IvParameterSpec;
52e0732e 19import javax.crypto.spec.SecretKeySpec;
9fb03c36 20import 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 */
35public 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}