fix Base64 but breaks compat
[nikiroo-utils.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;
12784931 7import java.security.InvalidAlgorithmParameterException;
52e0732e
NR
8import java.security.InvalidKeyException;
9import java.security.NoSuchAlgorithmException;
10
11import javax.crypto.BadPaddingException;
12import javax.crypto.Cipher;
d20c8d77
NR
13import javax.crypto.CipherInputStream;
14import javax.crypto.CipherOutputStream;
52e0732e
NR
15import javax.crypto.IllegalBlockSizeException;
16import javax.crypto.NoSuchPaddingException;
17import javax.crypto.SecretKey;
12784931 18import javax.crypto.spec.IvParameterSpec;
52e0732e 19import javax.crypto.spec.SecretKeySpec;
9fb03c36 20import javax.net.ssl.SSLException;
52e0732e 21
a6a73de3
NR
22import be.nikiroo.utils.streams.Base64InputStream;
23import be.nikiroo.utils.streams.Base64OutputStream;
24
52e0732e
NR
25/**
26 * Small utility class to do AES encryption/decryption.
27 * <p>
0747c3c2
NR
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>
52e0732e
NR
34 * Do not assume it is actually secure until you checked the code...
35 *
36 * @author niki
37 */
38public class CryptUtils {
12784931
NR
39 static private final String AES_NAME = "AES/CFB8/NoPadding";
40
52e0732e
NR
41 private Cipher ecipher;
42 private Cipher dcipher;
0747c3c2 43 private SecretKey key;
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
0747c3c2
NR
217 key = new SecretKeySpec(bytes32, "AES");
218 ecipher = newCipher(Cipher.ENCRYPT_MODE);
219 dcipher = newCipher(Cipher.DECRYPT_MODE);
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 {
12784931
NR
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);
0747c3c2 236 Cipher cipher = Cipher.getInstance(AES_NAME);
12784931 237 cipher.init(mode, key, ivspec);
0747c3c2 238 return cipher;
52e0732e
NR
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();
12784931
NR
251 } catch (InvalidAlgorithmParameterException e) {
252 // Woops?
253 e.printStackTrace();
52e0732e 254 }
12784931 255
0747c3c2 256 return null;
52e0732e
NR
257 }
258
259 /**
260 * Encrypt the data.
261 *
262 * @param data
263 * the data to encrypt
264 *
265 * @return the encrypted data
266 *
9fb03c36 267 * @throws SSLException
52e0732e
NR
268 * in case of I/O error (i.e., the data is not what you assumed
269 * it was)
270 */
9fb03c36 271 public byte[] encrypt(byte[] data) throws SSLException {
0747c3c2
NR
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 }
52e0732e
NR
280 }
281 }
282
283 /**
284 * Encrypt the data.
285 *
286 * @param data
287 * the data to encrypt
288 *
289 * @return the encrypted data
290 *
9fb03c36 291 * @throws SSLException
52e0732e
NR
292 * in case of I/O error (i.e., the data is not what you assumed
293 * it was)
294 */
9fb03c36
NR
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 }
52e0732e
NR
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 *
9fb03c36 317 * @throws SSLException
52e0732e
NR
318 * in case of I/O error (i.e., the data is not what you assumed
319 * it was)
320 */
a6a73de3 321 public String encrypt64(String data) throws SSLException {
9fb03c36 322 try {
a6a73de3 323 return encrypt64(data.getBytes("UTF8"));
9fb03c36
NR
324 } catch (UnsupportedEncodingException e) {
325 // UTF-8 is required in all confirm JVMs
326 e.printStackTrace();
327 return null;
328 }
52e0732e
NR
329 }
330
331 /**
332 * Encrypt the data, then encode it into Base64.
333 *
334 * @param data
335 * the data to encrypt
52e0732e
NR
336 *
337 * @return the encrypted data, encoded in Base64
338 *
9fb03c36 339 * @throws SSLException
52e0732e
NR
340 * in case of I/O error (i.e., the data is not what you assumed
341 * it was)
342 */
a6a73de3 343 public String encrypt64(byte[] data) throws SSLException {
9fb03c36 344 try {
a6a73de3 345 return StringUtils.base64(encrypt(data));
9fb03c36
NR
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 }
52e0732e
NR
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 *
9fb03c36 361 * @throws SSLException
52e0732e
NR
362 * in case of I/O error
363 */
9fb03c36 364 public byte[] decrypt(byte[] data) throws SSLException {
0747c3c2
NR
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 }
52e0732e
NR
373 }
374 }
375
376 /**
377 * Decode the data which is assumed to be encrypted with the same utilities
ed3aa598
NR
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
52e0732e
NR
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 *
9fb03c36 410 * @throws SSLException
52e0732e
NR
411 * in case of I/O error
412 */
a6a73de3 413 public byte[] decrypt64(String data) throws SSLException {
9fb03c36 414 try {
a6a73de3 415 return decrypt(StringUtils.unbase64(data));
9fb03c36
NR
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 }
52e0732e
NR
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 *
9fb03c36 436 * @throws SSLException
52e0732e
NR
437 * in case of I/O error
438 */
a6a73de3 439 public String decrypt64s(String data) throws SSLException {
9fb03c36 440 try {
a6a73de3 441 return new String(decrypt(StringUtils.unbase64(data)), "UTF-8");
9fb03c36
NR
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 }
52e0732e
NR
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) {
b1ed544b 462 return StringUtils.getMd5Hash("" + input).getBytes();
52e0732e
NR
463 }
464}