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