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