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; |
52e0732e | 7 | import java.security.InvalidKeyException; |
52e0732e NR |
8 | |
9 | import javax.crypto.BadPaddingException; | |
10 | import javax.crypto.Cipher; | |
d20c8d77 NR |
11 | import javax.crypto.CipherInputStream; |
12 | import javax.crypto.CipherOutputStream; | |
52e0732e | 13 | import javax.crypto.IllegalBlockSizeException; |
52e0732e | 14 | import javax.crypto.SecretKey; |
12784931 | 15 | import javax.crypto.spec.IvParameterSpec; |
52e0732e | 16 | import javax.crypto.spec.SecretKeySpec; |
9fb03c36 | 17 | import javax.net.ssl.SSLException; |
52e0732e | 18 | |
a6a73de3 NR |
19 | import be.nikiroo.utils.streams.Base64InputStream; |
20 | import be.nikiroo.utils.streams.Base64OutputStream; | |
21 | ||
52e0732e NR |
22 | /** |
23 | * Small utility class to do AES encryption/decryption. | |
24 | * <p> | |
0747c3c2 NR |
25 | * For the moment, it is multi-thread compatible, but beware: |
26 | * <ul> | |
27 | * <li>The encrypt/decrypt calls are serialized</li> | |
28 | * <li>The streams are independent and thus parallel</li> | |
29 | * </ul> | |
30 | * <p> | |
e61669f9 NR |
31 | * Do not assume it is actually secure, it is actually not. |
32 | * <p> | |
33 | * It just here to offer a more-or-less protected exchange of data because | |
34 | * anonymous and self-signed certificates backed SSL is against Google wishes | |
35 | * (so, don't even try, they own Internet). | |
52e0732e NR |
36 | * |
37 | * @author niki | |
38 | */ | |
39 | public class CryptUtils { | |
e61669f9 | 40 | static private final String AES_NAME = "AES/CFB128/NoPadding"; |
12784931 | 41 | |
52e0732e NR |
42 | private Cipher ecipher; |
43 | private Cipher dcipher; | |
0747c3c2 | 44 | private SecretKey key; |
52e0732e NR |
45 | |
46 | /** | |
12784931 NR |
47 | * Small and lazy-easy way to initialize a 128 bits key with |
48 | * {@link CryptUtils}. | |
52e0732e NR |
49 | * <p> |
50 | * <b>Some</b> part of the key will be used to generate a 128 bits key and | |
51 | * initialize the {@link CryptUtils}; even NULL will generate something. | |
52 | * <p> | |
53 | * <b>This is most probably not secure. Do not use if you actually care | |
54 | * about security.</b> | |
55 | * | |
56 | * @param key | |
57 | * the {@link String} to use as a base for the key, can be NULL | |
58 | */ | |
59 | public CryptUtils(String key) { | |
60 | try { | |
0747c3c2 | 61 | init(key2key(key)); |
52e0732e NR |
62 | } catch (InvalidKeyException e) { |
63 | // We made sure that the key is correct, so nothing here | |
64 | e.printStackTrace(); | |
65 | } | |
66 | } | |
67 | ||
68 | /** | |
69 | * Create a new instance of {@link CryptUtils} with the given 128 bytes key. | |
70 | * <p> | |
71 | * The key <b>must</b> be exactly 128 bytes long. | |
72 | * | |
73 | * @param bytes32 | |
74 | * the 128 bits (32 bytes) of the key | |
75 | * | |
76 | * @throws InvalidKeyException | |
77 | * if the key is not an array of 128 bytes | |
78 | */ | |
79 | public CryptUtils(byte[] bytes32) throws InvalidKeyException { | |
80 | init(bytes32); | |
81 | } | |
82 | ||
d20c8d77 NR |
83 | /** |
84 | * Wrap the given {@link InputStream} so it is transparently encrypted by | |
85 | * the current {@link CryptUtils}. | |
86 | * | |
87 | * @param in | |
88 | * the {@link InputStream} to wrap | |
89 | * @return the auto-encode {@link InputStream} | |
90 | */ | |
12784931 | 91 | public InputStream encrypt(InputStream in) { |
0747c3c2 | 92 | Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); |
d20c8d77 NR |
93 | return new CipherInputStream(in, ecipher); |
94 | } | |
95 | ||
12784931 NR |
96 | /** |
97 | * Wrap the given {@link InputStream} so it is transparently encrypted by | |
98 | * the current {@link CryptUtils} and encoded in base64. | |
99 | * | |
100 | * @param in | |
101 | * the {@link InputStream} to wrap | |
12784931 NR |
102 | * |
103 | * @return the auto-encode {@link InputStream} | |
104 | * | |
105 | * @throws IOException | |
106 | * in case of I/O error | |
107 | */ | |
a6a73de3 NR |
108 | public InputStream encrypt64(InputStream in) throws IOException { |
109 | return new Base64InputStream(encrypt(in), true); | |
12784931 NR |
110 | } |
111 | ||
d20c8d77 NR |
112 | /** |
113 | * Wrap the given {@link OutputStream} so it is transparently encrypted by | |
114 | * the current {@link CryptUtils}. | |
115 | * | |
12784931 | 116 | * @param out |
d20c8d77 | 117 | * the {@link OutputStream} to wrap |
12784931 | 118 | * |
d20c8d77 NR |
119 | * @return the auto-encode {@link OutputStream} |
120 | */ | |
12784931 | 121 | public OutputStream encrypt(OutputStream out) { |
0747c3c2 | 122 | Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE); |
d20c8d77 NR |
123 | return new CipherOutputStream(out, ecipher); |
124 | } | |
125 | ||
126 | /** | |
12784931 NR |
127 | * Wrap the given {@link OutputStream} so it is transparently encrypted by |
128 | * the current {@link CryptUtils} and encoded in base64. | |
129 | * | |
130 | * @param out | |
131 | * the {@link OutputStream} to wrap | |
12784931 NR |
132 | * |
133 | * @return the auto-encode {@link OutputStream} | |
134 | * | |
135 | * @throws IOException | |
136 | * in case of I/O error | |
137 | */ | |
a6a73de3 NR |
138 | public OutputStream encrypt64(OutputStream out) throws IOException { |
139 | return encrypt(new Base64OutputStream(out, true)); | |
12784931 NR |
140 | } |
141 | ||
142 | /** | |
143 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
d20c8d77 NR |
144 | * current {@link CryptUtils}. |
145 | * | |
146 | * @param in | |
147 | * the {@link InputStream} to wrap | |
12784931 | 148 | * |
d20c8d77 NR |
149 | * @return the auto-decode {@link InputStream} |
150 | */ | |
12784931 | 151 | public InputStream decrypt(InputStream in) { |
0747c3c2 | 152 | Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); |
d20c8d77 NR |
153 | return new CipherInputStream(in, dcipher); |
154 | } | |
155 | ||
156 | /** | |
12784931 NR |
157 | * Wrap the given {@link OutputStream} so it is transparently decoded by the |
158 | * current {@link CryptUtils} and decoded from base64. | |
159 | * | |
160 | * @param in | |
161 | * the {@link InputStream} to wrap | |
12784931 NR |
162 | * |
163 | * @return the auto-decode {@link InputStream} | |
164 | * | |
165 | * @throws IOException | |
166 | * in case of I/O error | |
167 | */ | |
a6a73de3 NR |
168 | public InputStream decrypt64(InputStream in) throws IOException { |
169 | return decrypt(new Base64InputStream(in, false)); | |
12784931 NR |
170 | } |
171 | ||
172 | /** | |
173 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
d20c8d77 NR |
174 | * current {@link CryptUtils}. |
175 | * | |
176 | * @param out | |
177 | * the {@link OutputStream} to wrap | |
178 | * @return the auto-decode {@link OutputStream} | |
179 | */ | |
12784931 | 180 | public OutputStream decrypt(OutputStream out) { |
0747c3c2 | 181 | Cipher dcipher = newCipher(Cipher.DECRYPT_MODE); |
d20c8d77 NR |
182 | return new CipherOutputStream(out, dcipher); |
183 | } | |
184 | ||
12784931 NR |
185 | /** |
186 | * Wrap the given {@link OutputStream} so it is transparently decoded by the | |
187 | * current {@link CryptUtils} and decoded from base64. | |
188 | * | |
189 | * @param out | |
190 | * the {@link OutputStream} to wrap | |
12784931 NR |
191 | * |
192 | * @return the auto-decode {@link OutputStream} | |
193 | * | |
194 | * @throws IOException | |
195 | * in case of I/O error | |
196 | */ | |
a6a73de3 NR |
197 | public OutputStream decrypt64(OutputStream out) throws IOException { |
198 | return new Base64OutputStream(decrypt(out), false); | |
12784931 NR |
199 | } |
200 | ||
52e0732e NR |
201 | /** |
202 | * This method required an array of 128 bytes. | |
203 | * | |
204 | * @param bytes32 | |
205 | * the array, which <b>must</b> be of 128 bits (32 bytes) | |
206 | * | |
207 | * @throws InvalidKeyException | |
208 | * if the key is not an array of 128 bits (32 bytes) | |
209 | */ | |
210 | private void init(byte[] bytes32) throws InvalidKeyException { | |
211 | if (bytes32 == null || bytes32.length != 32) { | |
212 | throw new InvalidKeyException( | |
213 | "The size of the key must be of 128 bits (32 bytes), it is: " | |
214 | + (bytes32 == null ? "null" : "" + bytes32.length) | |
215 | + " bytes"); | |
216 | } | |
217 | ||
0747c3c2 NR |
218 | key = new SecretKeySpec(bytes32, "AES"); |
219 | ecipher = newCipher(Cipher.ENCRYPT_MODE); | |
220 | dcipher = newCipher(Cipher.DECRYPT_MODE); | |
221 | } | |
12784931 | 222 | |
0747c3c2 NR |
223 | /** |
224 | * Create a new {@link Cipher}of the given mode (see | |
225 | * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}). | |
226 | * | |
227 | * @param mode | |
228 | * the mode ({@link Cipher#ENCRYPT_MODE} or | |
229 | * {@link Cipher#ENCRYPT_MODE}) | |
230 | * | |
231 | * @return the new {@link Cipher} | |
232 | */ | |
233 | private Cipher newCipher(int mode) { | |
52e0732e | 234 | try { |
12784931 NR |
235 | byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
236 | IvParameterSpec ivspec = new IvParameterSpec(iv); | |
0747c3c2 | 237 | Cipher cipher = Cipher.getInstance(AES_NAME); |
12784931 | 238 | cipher.init(mode, key, ivspec); |
0747c3c2 | 239 | return cipher; |
e61669f9 | 240 | } catch (Exception e) { |
12784931 | 241 | e.printStackTrace(); |
e61669f9 NR |
242 | throw new RuntimeException( |
243 | "Cannot initialize encryption sub-system", e); | |
52e0732e NR |
244 | } |
245 | } | |
246 | ||
247 | /** | |
248 | * Encrypt the data. | |
249 | * | |
250 | * @param data | |
251 | * the data to encrypt | |
252 | * | |
253 | * @return the encrypted data | |
254 | * | |
9fb03c36 | 255 | * @throws SSLException |
52e0732e NR |
256 | * in case of I/O error (i.e., the data is not what you assumed |
257 | * it was) | |
258 | */ | |
9fb03c36 | 259 | public byte[] encrypt(byte[] data) throws SSLException { |
0747c3c2 NR |
260 | synchronized (ecipher) { |
261 | try { | |
262 | return ecipher.doFinal(data); | |
263 | } catch (IllegalBlockSizeException e) { | |
264 | throw new SSLException(e); | |
265 | } catch (BadPaddingException e) { | |
266 | throw new SSLException(e); | |
267 | } | |
52e0732e NR |
268 | } |
269 | } | |
270 | ||
271 | /** | |
272 | * Encrypt the data. | |
273 | * | |
274 | * @param data | |
275 | * the data to encrypt | |
276 | * | |
277 | * @return the encrypted data | |
278 | * | |
9fb03c36 | 279 | * @throws SSLException |
52e0732e NR |
280 | * in case of I/O error (i.e., the data is not what you assumed |
281 | * it was) | |
282 | */ | |
9fb03c36 | 283 | public byte[] encrypt(String data) throws SSLException { |
f8147a0e | 284 | return encrypt(StringUtils.getBytes(data)); |
52e0732e NR |
285 | } |
286 | ||
287 | /** | |
288 | * Encrypt the data, then encode it into Base64. | |
289 | * | |
290 | * @param data | |
291 | * the data to encrypt | |
292 | * @param zip | |
293 | * TRUE to also compress the data in GZIP format; remember that | |
294 | * compressed and not-compressed content are different; you need | |
295 | * to know which is which when decoding | |
296 | * | |
297 | * @return the encrypted data, encoded in Base64 | |
298 | * | |
9fb03c36 | 299 | * @throws SSLException |
52e0732e NR |
300 | * in case of I/O error (i.e., the data is not what you assumed |
301 | * it was) | |
302 | */ | |
a6a73de3 | 303 | public String encrypt64(String data) throws SSLException { |
f8147a0e | 304 | return encrypt64(StringUtils.getBytes(data)); |
52e0732e NR |
305 | } |
306 | ||
307 | /** | |
308 | * Encrypt the data, then encode it into Base64. | |
309 | * | |
310 | * @param data | |
311 | * the data to encrypt | |
52e0732e NR |
312 | * |
313 | * @return the encrypted data, encoded in Base64 | |
314 | * | |
9fb03c36 | 315 | * @throws SSLException |
52e0732e NR |
316 | * in case of I/O error (i.e., the data is not what you assumed |
317 | * it was) | |
318 | */ | |
a6a73de3 | 319 | public String encrypt64(byte[] data) throws SSLException { |
9fb03c36 | 320 | try { |
a6a73de3 | 321 | return StringUtils.base64(encrypt(data)); |
9fb03c36 NR |
322 | } catch (IOException e) { |
323 | // not exactly true, but we consider here that this error is a crypt | |
324 | // error, not a normal I/O error | |
325 | throw new SSLException(e); | |
326 | } | |
52e0732e NR |
327 | } |
328 | ||
329 | /** | |
330 | * Decode the data which is assumed to be encrypted with the same utilities. | |
331 | * | |
332 | * @param data | |
333 | * the encrypted data to decode | |
334 | * | |
335 | * @return the original, decoded data | |
336 | * | |
9fb03c36 | 337 | * @throws SSLException |
52e0732e NR |
338 | * in case of I/O error |
339 | */ | |
9fb03c36 | 340 | public byte[] decrypt(byte[] data) throws SSLException { |
0747c3c2 NR |
341 | synchronized (dcipher) { |
342 | try { | |
343 | return dcipher.doFinal(data); | |
344 | } catch (IllegalBlockSizeException e) { | |
345 | throw new SSLException(e); | |
346 | } catch (BadPaddingException e) { | |
347 | throw new SSLException(e); | |
348 | } | |
52e0732e NR |
349 | } |
350 | } | |
351 | ||
352 | /** | |
353 | * Decode the data which is assumed to be encrypted with the same utilities | |
ed3aa598 NR |
354 | * and to be a {@link String}. |
355 | * | |
356 | * @param data | |
357 | * the encrypted data to decode | |
358 | * | |
359 | * @return the original, decoded data,as a {@link String} | |
360 | * | |
361 | * @throws SSLException | |
362 | * in case of I/O error | |
363 | */ | |
364 | public String decrypts(byte[] data) throws SSLException { | |
365 | try { | |
366 | return new String(decrypt(data), "UTF-8"); | |
367 | } catch (UnsupportedEncodingException e) { | |
368 | // UTF-8 is required in all confirm JVMs | |
369 | e.printStackTrace(); | |
370 | return null; | |
371 | } | |
372 | } | |
373 | ||
374 | /** | |
375 | * Decode the data which is assumed to be encrypted with the same utilities | |
52e0732e NR |
376 | * and is a Base64 encoded value. |
377 | * | |
378 | * @param data | |
379 | * the encrypted data to decode in Base64 format | |
380 | * @param zip | |
381 | * TRUE to also uncompress the data from a GZIP format | |
382 | * automatically; if set to FALSE, zipped data can be returned | |
383 | * | |
384 | * @return the original, decoded data | |
385 | * | |
9fb03c36 | 386 | * @throws SSLException |
52e0732e NR |
387 | * in case of I/O error |
388 | */ | |
a6a73de3 | 389 | public byte[] decrypt64(String data) throws SSLException { |
9fb03c36 | 390 | try { |
a6a73de3 | 391 | return decrypt(StringUtils.unbase64(data)); |
9fb03c36 NR |
392 | } catch (IOException e) { |
393 | // not exactly true, but we consider here that this error is a crypt | |
394 | // error, not a normal I/O error | |
395 | throw new SSLException(e); | |
396 | } | |
52e0732e NR |
397 | } |
398 | ||
399 | /** | |
400 | * Decode the data which is assumed to be encrypted with the same utilities | |
401 | * and is a Base64 encoded value, then convert it into a String (this method | |
402 | * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}). | |
403 | * | |
404 | * @param data | |
405 | * the encrypted data to decode in Base64 format | |
406 | * @param zip | |
407 | * TRUE to also uncompress the data from a GZIP format | |
408 | * automatically; if set to FALSE, zipped data can be returned | |
409 | * | |
410 | * @return the original, decoded data | |
411 | * | |
9fb03c36 | 412 | * @throws SSLException |
52e0732e NR |
413 | * in case of I/O error |
414 | */ | |
a6a73de3 | 415 | public String decrypt64s(String data) throws SSLException { |
9fb03c36 | 416 | try { |
a6a73de3 | 417 | return new String(decrypt(StringUtils.unbase64(data)), "UTF-8"); |
9fb03c36 NR |
418 | } catch (UnsupportedEncodingException e) { |
419 | // UTF-8 is required in all confirm JVMs | |
420 | e.printStackTrace(); | |
421 | return null; | |
422 | } catch (IOException e) { | |
423 | // not exactly true, but we consider here that this error is a crypt | |
424 | // error, not a normal I/O error | |
425 | throw new SSLException(e); | |
426 | } | |
52e0732e NR |
427 | } |
428 | ||
429 | /** | |
430 | * This is probably <b>NOT</b> secure! | |
431 | * | |
432 | * @param input | |
433 | * some {@link String} input | |
434 | * | |
435 | * @return a 128 bits key computed from the given input | |
436 | */ | |
437 | static private byte[] key2key(String input) { | |
b1ed544b | 438 | return StringUtils.getMd5Hash("" + input).getBytes(); |
52e0732e NR |
439 | } |
440 | } |