crypt: init the IV
[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.spec.IvParameterSpec;
15 import javax.crypto.spec.SecretKeySpec;
16 import javax.net.ssl.SSLException;
17
18 import be.nikiroo.utils.streams.Base64InputStream;
19 import be.nikiroo.utils.streams.Base64OutputStream;
20
21 /**
22 * Small utility class to do AES encryption/decryption.
23 * <p>
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>
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).
35 *
36 * @author niki
37 */
38 public class CryptUtils {
39 static private final String AES_NAME = "AES/CFB128/NoPadding";
40
41 private Cipher ecipher;
42 private Cipher dcipher;
43 private byte[] bytes32;
44
45 /**
46 * Small and lazy-easy way to initialize a 128 bits key with
47 * {@link CryptUtils}.
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 {
60 init(key2key(key));
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
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 */
90 public InputStream encrypt(InputStream in) {
91 Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE);
92 return new CipherInputStream(in, ecipher);
93 }
94
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
101 *
102 * @return the auto-encode {@link InputStream}
103 *
104 * @throws IOException
105 * in case of I/O error
106 */
107 public InputStream encrypt64(InputStream in) throws IOException {
108 return new Base64InputStream(encrypt(in), true);
109 }
110
111 /**
112 * Wrap the given {@link OutputStream} so it is transparently encrypted by
113 * the current {@link CryptUtils}.
114 *
115 * @param out
116 * the {@link OutputStream} to wrap
117 *
118 * @return the auto-encode {@link OutputStream}
119 */
120 public OutputStream encrypt(OutputStream out) {
121 Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE);
122 return new CipherOutputStream(out, ecipher);
123 }
124
125 /**
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
131 *
132 * @return the auto-encode {@link OutputStream}
133 *
134 * @throws IOException
135 * in case of I/O error
136 */
137 public OutputStream encrypt64(OutputStream out) throws IOException {
138 return encrypt(new Base64OutputStream(out, true));
139 }
140
141 /**
142 * Wrap the given {@link OutputStream} so it is transparently decoded by the
143 * current {@link CryptUtils}.
144 *
145 * @param in
146 * the {@link InputStream} to wrap
147 *
148 * @return the auto-decode {@link InputStream}
149 */
150 public InputStream decrypt(InputStream in) {
151 Cipher dcipher = newCipher(Cipher.DECRYPT_MODE);
152 return new CipherInputStream(in, dcipher);
153 }
154
155 /**
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
161 *
162 * @return the auto-decode {@link InputStream}
163 *
164 * @throws IOException
165 * in case of I/O error
166 */
167 public InputStream decrypt64(InputStream in) throws IOException {
168 return decrypt(new Base64InputStream(in, false));
169 }
170
171 /**
172 * Wrap the given {@link OutputStream} so it is transparently decoded by the
173 * current {@link CryptUtils}.
174 *
175 * @param out
176 * the {@link OutputStream} to wrap
177 * @return the auto-decode {@link OutputStream}
178 */
179 public OutputStream decrypt(OutputStream out) {
180 Cipher dcipher = newCipher(Cipher.DECRYPT_MODE);
181 return new CipherOutputStream(out, dcipher);
182 }
183
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
190 *
191 * @return the auto-decode {@link OutputStream}
192 *
193 * @throws IOException
194 * in case of I/O error
195 */
196 public OutputStream decrypt64(OutputStream out) throws IOException {
197 return new Base64OutputStream(decrypt(out), false);
198 }
199
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
217 this.bytes32 = bytes32;
218 this.ecipher = newCipher(Cipher.ENCRYPT_MODE);
219 this.dcipher = newCipher(Cipher.DECRYPT_MODE);
220 }
221
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) {
233 try {
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 }
239 IvParameterSpec ivspec = new IvParameterSpec(iv);
240 Cipher cipher = Cipher.getInstance(AES_NAME);
241 cipher.init(mode, new SecretKeySpec(bytes32, "AES"), ivspec);
242 return cipher;
243 } catch (Exception e) {
244 e.printStackTrace();
245 throw new RuntimeException(
246 "Cannot initialize encryption sub-system", e);
247 }
248 }
249
250 /**
251 * Encrypt the data.
252 *
253 * @param data
254 * the data to encrypt
255 *
256 * @return the encrypted data
257 *
258 * @throws SSLException
259 * in case of I/O error (i.e., the data is not what you assumed
260 * it was)
261 */
262 public byte[] encrypt(byte[] data) throws SSLException {
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 }
271 }
272 }
273
274 /**
275 * Encrypt the data.
276 *
277 * @param data
278 * the data to encrypt
279 *
280 * @return the encrypted data
281 *
282 * @throws SSLException
283 * in case of I/O error (i.e., the data is not what you assumed
284 * it was)
285 */
286 public byte[] encrypt(String data) throws SSLException {
287 return encrypt(StringUtils.getBytes(data));
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 *
302 * @throws SSLException
303 * in case of I/O error (i.e., the data is not what you assumed
304 * it was)
305 */
306 public String encrypt64(String data) throws SSLException {
307 return encrypt64(StringUtils.getBytes(data));
308 }
309
310 /**
311 * Encrypt the data, then encode it into Base64.
312 *
313 * @param data
314 * the data to encrypt
315 *
316 * @return the encrypted data, encoded in Base64
317 *
318 * @throws SSLException
319 * in case of I/O error (i.e., the data is not what you assumed
320 * it was)
321 */
322 public String encrypt64(byte[] data) throws SSLException {
323 try {
324 return StringUtils.base64(encrypt(data));
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 }
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 *
340 * @throws SSLException
341 * in case of I/O error
342 */
343 public byte[] decrypt(byte[] data) throws SSLException {
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 }
352 }
353 }
354
355 /**
356 * Decode the data which is assumed to be encrypted with the same utilities
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
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 *
389 * @throws SSLException
390 * in case of I/O error
391 */
392 public byte[] decrypt64(String data) throws SSLException {
393 try {
394 return decrypt(StringUtils.unbase64(data));
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 }
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 *
415 * @throws SSLException
416 * in case of I/O error
417 */
418 public String decrypt64s(String data) throws SSLException {
419 try {
420 return new String(decrypt(StringUtils.unbase64(data)), "UTF-8");
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 }
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) {
441 return StringUtils.getMd5Hash("" + input).getBytes();
442 }
443 }