Commit | Line | Data |
---|---|---|
52e0732e NR |
1 | package be.nikiroo.utils; |
2 | ||
3 | import java.io.IOException; | |
d20c8d77 NR |
4 | import java.io.InputStream; |
5 | import java.io.OutputStream; | |
9fb03c36 | 6 | import java.io.UnsupportedEncodingException; |
12784931 | 7 | import java.security.InvalidAlgorithmParameterException; |
52e0732e NR |
8 | import java.security.InvalidKeyException; |
9 | import java.security.NoSuchAlgorithmException; | |
10 | ||
11 | import javax.crypto.BadPaddingException; | |
12 | import javax.crypto.Cipher; | |
d20c8d77 NR |
13 | import javax.crypto.CipherInputStream; |
14 | import javax.crypto.CipherOutputStream; | |
52e0732e NR |
15 | import javax.crypto.IllegalBlockSizeException; |
16 | import javax.crypto.NoSuchPaddingException; | |
17 | import javax.crypto.SecretKey; | |
12784931 | 18 | import javax.crypto.spec.IvParameterSpec; |
52e0732e | 19 | import javax.crypto.spec.SecretKeySpec; |
9fb03c36 | 20 | import javax.net.ssl.SSLException; |
52e0732e | 21 | |
a6a73de3 NR |
22 | import be.nikiroo.utils.streams.Base64InputStream; |
23 | import 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 | */ | |
38 | public 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 | 295 | public byte[] encrypt(String data) throws SSLException { |
f8147a0e | 296 | return encrypt(StringUtils.getBytes(data)); |
52e0732e NR |
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 | * | |
9fb03c36 | 311 | * @throws SSLException |
52e0732e NR |
312 | * in case of I/O error (i.e., the data is not what you assumed |
313 | * it was) | |
314 | */ | |
a6a73de3 | 315 | public String encrypt64(String data) throws SSLException { |
f8147a0e | 316 | return encrypt64(StringUtils.getBytes(data)); |
52e0732e NR |
317 | } |
318 | ||
319 | /** | |
320 | * Encrypt the data, then encode it into Base64. | |
321 | * | |
322 | * @param data | |
323 | * the data to encrypt | |
52e0732e NR |
324 | * |
325 | * @return the encrypted data, encoded in Base64 | |
326 | * | |
9fb03c36 | 327 | * @throws SSLException |
52e0732e NR |
328 | * in case of I/O error (i.e., the data is not what you assumed |
329 | * it was) | |
330 | */ | |
a6a73de3 | 331 | public String encrypt64(byte[] data) throws SSLException { |
9fb03c36 | 332 | try { |
a6a73de3 | 333 | return StringUtils.base64(encrypt(data)); |
9fb03c36 NR |
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 | } | |
52e0732e NR |
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 | * | |
9fb03c36 | 349 | * @throws SSLException |
52e0732e NR |
350 | * in case of I/O error |
351 | */ | |
9fb03c36 | 352 | public byte[] decrypt(byte[] data) throws SSLException { |
0747c3c2 NR |
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 | } | |
52e0732e NR |
361 | } |
362 | } | |
363 | ||
364 | /** | |
365 | * Decode the data which is assumed to be encrypted with the same utilities | |
ed3aa598 NR |
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 | |
52e0732e NR |
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 | * | |
9fb03c36 | 398 | * @throws SSLException |
52e0732e NR |
399 | * in case of I/O error |
400 | */ | |
a6a73de3 | 401 | public byte[] decrypt64(String data) throws SSLException { |
9fb03c36 | 402 | try { |
a6a73de3 | 403 | return decrypt(StringUtils.unbase64(data)); |
9fb03c36 NR |
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 | } | |
52e0732e NR |
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 | * | |
9fb03c36 | 424 | * @throws SSLException |
52e0732e NR |
425 | * in case of I/O error |
426 | */ | |
a6a73de3 | 427 | public String decrypt64s(String data) throws SSLException { |
9fb03c36 | 428 | try { |
a6a73de3 | 429 | return new String(decrypt(StringUtils.unbase64(data)), "UTF-8"); |
9fb03c36 NR |
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 | } | |
52e0732e NR |
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) { | |
b1ed544b | 450 | return StringUtils.getMd5Hash("" + input).getBytes(); |
52e0732e NR |
451 | } |
452 | } |