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