ed6f9e08a04cb623823863e5ded858ad6b6d9748
[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.InvalidKeyException;
8 import java.security.NoSuchAlgorithmException;
9
10 import javax.crypto.BadPaddingException;
11 import javax.crypto.Cipher;
12 import javax.crypto.CipherInputStream;
13 import javax.crypto.CipherOutputStream;
14 import javax.crypto.IllegalBlockSizeException;
15 import javax.crypto.NoSuchPaddingException;
16 import javax.crypto.SecretKey;
17 import javax.crypto.spec.SecretKeySpec;
18 import javax.net.ssl.SSLException;
19
20 /**
21 * Small utility class to do AES encryption/decryption.
22 * <p>
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>
29 * Do not assume it is actually secure until you checked the code...
30 *
31 * @author niki
32 */
33 public class CryptUtils {
34 static private final String AES_NAME = "AES/ECB/PKCS5Padding";
35
36 private Cipher ecipher;
37 private Cipher dcipher;
38 private SecretKey key;
39
40 /**
41 * Small and lazy-easy way to initialize a 128 bits key with {@link CryptUtils}.
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 {
54 init(key2key(key));
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
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) {
85 Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE);
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) {
98 Cipher ecipher = newCipher(Cipher.ENCRYPT_MODE);
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) {
111 Cipher dcipher = newCipher(Cipher.DECRYPT_MODE);
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) {
124 Cipher dcipher = newCipher(Cipher.DECRYPT_MODE);
125 return new CipherOutputStream(out, dcipher);
126 }
127
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
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) {
161 try {
162 Cipher cipher = Cipher.getInstance(AES_NAME);
163 cipher.init(mode, key);
164 return cipher;
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 }
178
179 return null;
180 }
181
182 /**
183 * Encrypt the data.
184 *
185 * @param data
186 * the data to encrypt
187 *
188 * @return the encrypted data
189 *
190 * @throws SSLException
191 * in case of I/O error (i.e., the data is not what you assumed
192 * it was)
193 */
194 public byte[] encrypt(byte[] data) throws SSLException {
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 }
203 }
204 }
205
206 /**
207 * Encrypt the data.
208 *
209 * @param data
210 * the data to encrypt
211 *
212 * @return the encrypted data
213 *
214 * @throws SSLException
215 * in case of I/O error (i.e., the data is not what you assumed
216 * it was)
217 */
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 }
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 *
240 * @throws SSLException
241 * in case of I/O error (i.e., the data is not what you assumed
242 * it was)
243 */
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 }
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 *
266 * @throws SSLException
267 * in case of I/O error (i.e., the data is not what you assumed
268 * it was)
269 */
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 }
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 *
288 * @throws SSLException
289 * in case of I/O error
290 */
291 public byte[] decrypt(byte[] data) throws SSLException {
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 }
300 }
301 }
302
303 /**
304 * Decode the data which is assumed to be encrypted with the same utilities
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
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 *
337 * @throws SSLException
338 * in case of I/O error
339 */
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 }
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 *
363 * @throws SSLException
364 * in case of I/O error
365 */
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 }
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) {
389 return StringUtils.getMd5Hash("" + input).getBytes();
390 }
391 }