changelog
[fanfix.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;
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>
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 */
38public 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}