fix Base64 but breaks compat
[nikiroo-utils.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 try {
297 return encrypt(data.getBytes("UTF8"));
298 } catch (UnsupportedEncodingException e) {
299 // UTF-8 is required in all confirm JVMs
300 e.printStackTrace();
301 return null;
302 }
303 }
304
305 /**
306 * Encrypt the data, then encode it into Base64.
307 *
308 * @param data
309 * the data to encrypt
310 * @param zip
311 * TRUE to also compress the data in GZIP format; remember that
312 * compressed and not-compressed content are different; you need
313 * to know which is which when decoding
314 *
315 * @return the encrypted data, encoded in Base64
316 *
317 * @throws SSLException
318 * in case of I/O error (i.e., the data is not what you assumed
319 * it was)
320 */
321 public String encrypt64(String data) throws SSLException {
322 try {
323 return encrypt64(data.getBytes("UTF8"));
324 } catch (UnsupportedEncodingException e) {
325 // UTF-8 is required in all confirm JVMs
326 e.printStackTrace();
327 return null;
328 }
329 }
330
331 /**
332 * Encrypt the data, then encode it into Base64.
333 *
334 * @param data
335 * the data to encrypt
336 *
337 * @return the encrypted data, encoded in Base64
338 *
339 * @throws SSLException
340 * in case of I/O error (i.e., the data is not what you assumed
341 * it was)
342 */
343 public String encrypt64(byte[] data) throws SSLException {
344 try {
345 return StringUtils.base64(encrypt(data));
346 } catch (IOException e) {
347 // not exactly true, but we consider here that this error is a crypt
348 // error, not a normal I/O error
349 throw new SSLException(e);
350 }
351 }
352
353 /**
354 * Decode the data which is assumed to be encrypted with the same utilities.
355 *
356 * @param data
357 * the encrypted data to decode
358 *
359 * @return the original, decoded data
360 *
361 * @throws SSLException
362 * in case of I/O error
363 */
364 public byte[] decrypt(byte[] data) throws SSLException {
365 synchronized (dcipher) {
366 try {
367 return dcipher.doFinal(data);
368 } catch (IllegalBlockSizeException e) {
369 throw new SSLException(e);
370 } catch (BadPaddingException e) {
371 throw new SSLException(e);
372 }
373 }
374 }
375
376 /**
377 * Decode the data which is assumed to be encrypted with the same utilities
378 * and to be a {@link String}.
379 *
380 * @param data
381 * the encrypted data to decode
382 *
383 * @return the original, decoded data,as a {@link String}
384 *
385 * @throws SSLException
386 * in case of I/O error
387 */
388 public String decrypts(byte[] data) throws SSLException {
389 try {
390 return new String(decrypt(data), "UTF-8");
391 } catch (UnsupportedEncodingException e) {
392 // UTF-8 is required in all confirm JVMs
393 e.printStackTrace();
394 return null;
395 }
396 }
397
398 /**
399 * Decode the data which is assumed to be encrypted with the same utilities
400 * and is a Base64 encoded value.
401 *
402 * @param data
403 * the encrypted data to decode in Base64 format
404 * @param zip
405 * TRUE to also uncompress the data from a GZIP format
406 * automatically; if set to FALSE, zipped data can be returned
407 *
408 * @return the original, decoded data
409 *
410 * @throws SSLException
411 * in case of I/O error
412 */
413 public byte[] decrypt64(String data) throws SSLException {
414 try {
415 return decrypt(StringUtils.unbase64(data));
416 } catch (IOException e) {
417 // not exactly true, but we consider here that this error is a crypt
418 // error, not a normal I/O error
419 throw new SSLException(e);
420 }
421 }
422
423 /**
424 * Decode the data which is assumed to be encrypted with the same utilities
425 * and is a Base64 encoded value, then convert it into a String (this method
426 * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}).
427 *
428 * @param data
429 * the encrypted data to decode in Base64 format
430 * @param zip
431 * TRUE to also uncompress the data from a GZIP format
432 * automatically; if set to FALSE, zipped data can be returned
433 *
434 * @return the original, decoded data
435 *
436 * @throws SSLException
437 * in case of I/O error
438 */
439 public String decrypt64s(String data) throws SSLException {
440 try {
441 return new String(decrypt(StringUtils.unbase64(data)), "UTF-8");
442 } catch (UnsupportedEncodingException e) {
443 // UTF-8 is required in all confirm JVMs
444 e.printStackTrace();
445 return null;
446 } catch (IOException e) {
447 // not exactly true, but we consider here that this error is a crypt
448 // error, not a normal I/O error
449 throw new SSLException(e);
450 }
451 }
452
453 /**
454 * This is probably <b>NOT</b> secure!
455 *
456 * @param input
457 * some {@link String} input
458 *
459 * @return a 128 bits key computed from the given input
460 */
461 static private byte[] key2key(String input) {
462 return StringUtils.getMd5Hash("" + input).getBytes();
463 }
464 }