All getBytes("UTF-8") -> StringUtils
[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.InvalidAlgorithmParameterException;
8 import java.security.InvalidKeyException;
9 import java.security.NoSuchAlgorithmException;
10
11 import javax.crypto.BadPaddingException;
12 import javax.crypto.Cipher;
13 import javax.crypto.CipherInputStream;
14 import javax.crypto.CipherOutputStream;
15 import javax.crypto.IllegalBlockSizeException;
16 import javax.crypto.NoSuchPaddingException;
17 import javax.crypto.SecretKey;
18 import javax.crypto.spec.IvParameterSpec;
19 import javax.crypto.spec.SecretKeySpec;
20 import javax.net.ssl.SSLException;
21
22 import be.nikiroo.utils.streams.Base64InputStream;
23 import be.nikiroo.utils.streams.Base64OutputStream;
24
25 /**
26 * Small utility class to do AES encryption/decryption.
27 * <p>
28 * For the moment, it is multi-thread compatible, but beware:
29 * <ul>
30 * <li>The encrypt/decrypt calls are serialized</li>
31 * <li>The streams are independent and thus parallel</li>
32 * </ul>
33 * <p>
34 * Do not assume it is actually secure until you checked the code...
35 *
36 * @author niki
37 */
38 public class CryptUtils {
39 static private final String AES_NAME = "AES/CFB8/NoPadding";
40
41 private Cipher ecipher;
42 private Cipher dcipher;
43 private SecretKey key;
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 key = new SecretKeySpec(bytes32, "AES");
218 ecipher = newCipher(Cipher.ENCRYPT_MODE);
219 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 byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
235 IvParameterSpec ivspec = new IvParameterSpec(iv);
236 Cipher cipher = Cipher.getInstance(AES_NAME);
237 cipher.init(mode, key, ivspec);
238 return cipher;
239 } catch (NoSuchAlgorithmException e) {
240 // Every implementation of the Java platform is required to support
241 // this standard Cipher transformation with 128 bits keys
242 e.printStackTrace();
243 } catch (NoSuchPaddingException e) {
244 // Every implementation of the Java platform is required to support
245 // this standard Cipher transformation with 128 bits keys
246 e.printStackTrace();
247 } catch (InvalidKeyException e) {
248 // Every implementation of the Java platform is required to support
249 // this standard Cipher transformation with 128 bits keys
250 e.printStackTrace();
251 } catch (InvalidAlgorithmParameterException e) {
252 // Woops?
253 e.printStackTrace();
254 }
255
256 return null;
257 }
258
259 /**
260 * Encrypt the data.
261 *
262 * @param data
263 * the data to encrypt
264 *
265 * @return the encrypted data
266 *
267 * @throws SSLException
268 * in case of I/O error (i.e., the data is not what you assumed
269 * it was)
270 */
271 public byte[] encrypt(byte[] data) throws SSLException {
272 synchronized (ecipher) {
273 try {
274 return ecipher.doFinal(data);
275 } catch (IllegalBlockSizeException e) {
276 throw new SSLException(e);
277 } catch (BadPaddingException e) {
278 throw new SSLException(e);
279 }
280 }
281 }
282
283 /**
284 * Encrypt the data.
285 *
286 * @param data
287 * the data to encrypt
288 *
289 * @return the encrypted data
290 *
291 * @throws SSLException
292 * in case of I/O error (i.e., the data is not what you assumed
293 * it was)
294 */
295 public byte[] encrypt(String data) throws SSLException {
296 return encrypt(StringUtils.getBytes(data));
297 }
298
299 /**
300 * Encrypt the data, then encode it into Base64.
301 *
302 * @param data
303 * the data to encrypt
304 * @param zip
305 * TRUE to also compress the data in GZIP format; remember that
306 * compressed and not-compressed content are different; you need
307 * to know which is which when decoding
308 *
309 * @return the encrypted data, encoded in Base64
310 *
311 * @throws SSLException
312 * in case of I/O error (i.e., the data is not what you assumed
313 * it was)
314 */
315 public String encrypt64(String data) throws SSLException {
316 return encrypt64(StringUtils.getBytes(data));
317 }
318
319 /**
320 * Encrypt the data, then encode it into Base64.
321 *
322 * @param data
323 * the data to encrypt
324 *
325 * @return the encrypted data, encoded in Base64
326 *
327 * @throws SSLException
328 * in case of I/O error (i.e., the data is not what you assumed
329 * it was)
330 */
331 public String encrypt64(byte[] data) throws SSLException {
332 try {
333 return StringUtils.base64(encrypt(data));
334 } catch (IOException e) {
335 // not exactly true, but we consider here that this error is a crypt
336 // error, not a normal I/O error
337 throw new SSLException(e);
338 }
339 }
340
341 /**
342 * Decode the data which is assumed to be encrypted with the same utilities.
343 *
344 * @param data
345 * the encrypted data to decode
346 *
347 * @return the original, decoded data
348 *
349 * @throws SSLException
350 * in case of I/O error
351 */
352 public byte[] decrypt(byte[] data) throws SSLException {
353 synchronized (dcipher) {
354 try {
355 return dcipher.doFinal(data);
356 } catch (IllegalBlockSizeException e) {
357 throw new SSLException(e);
358 } catch (BadPaddingException e) {
359 throw new SSLException(e);
360 }
361 }
362 }
363
364 /**
365 * Decode the data which is assumed to be encrypted with the same utilities
366 * and to be a {@link String}.
367 *
368 * @param data
369 * the encrypted data to decode
370 *
371 * @return the original, decoded data,as a {@link String}
372 *
373 * @throws SSLException
374 * in case of I/O error
375 */
376 public String decrypts(byte[] data) throws SSLException {
377 try {
378 return new String(decrypt(data), "UTF-8");
379 } catch (UnsupportedEncodingException e) {
380 // UTF-8 is required in all confirm JVMs
381 e.printStackTrace();
382 return null;
383 }
384 }
385
386 /**
387 * Decode the data which is assumed to be encrypted with the same utilities
388 * and is a Base64 encoded value.
389 *
390 * @param data
391 * the encrypted data to decode in Base64 format
392 * @param zip
393 * TRUE to also uncompress the data from a GZIP format
394 * automatically; if set to FALSE, zipped data can be returned
395 *
396 * @return the original, decoded data
397 *
398 * @throws SSLException
399 * in case of I/O error
400 */
401 public byte[] decrypt64(String data) throws SSLException {
402 try {
403 return decrypt(StringUtils.unbase64(data));
404 } catch (IOException e) {
405 // not exactly true, but we consider here that this error is a crypt
406 // error, not a normal I/O error
407 throw new SSLException(e);
408 }
409 }
410
411 /**
412 * Decode the data which is assumed to be encrypted with the same utilities
413 * and is a Base64 encoded value, then convert it into a String (this method
414 * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}).
415 *
416 * @param data
417 * the encrypted data to decode in Base64 format
418 * @param zip
419 * TRUE to also uncompress the data from a GZIP format
420 * automatically; if set to FALSE, zipped data can be returned
421 *
422 * @return the original, decoded data
423 *
424 * @throws SSLException
425 * in case of I/O error
426 */
427 public String decrypt64s(String data) throws SSLException {
428 try {
429 return new String(decrypt(StringUtils.unbase64(data)), "UTF-8");
430 } catch (UnsupportedEncodingException e) {
431 // UTF-8 is required in all confirm JVMs
432 e.printStackTrace();
433 return null;
434 } catch (IOException e) {
435 // not exactly true, but we consider here that this error is a crypt
436 // error, not a normal I/O error
437 throw new SSLException(e);
438 }
439 }
440
441 /**
442 * This is probably <b>NOT</b> secure!
443 *
444 * @param input
445 * some {@link String} input
446 *
447 * @return a 128 bits key computed from the given input
448 */
449 static private byte[] key2key(String input) {
450 return StringUtils.getMd5Hash("" + input).getBytes();
451 }
452 }