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