1 package be
.nikiroo
.utils
;
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
;
9 import javax
.crypto
.BadPaddingException
;
10 import javax
.crypto
.Cipher
;
11 import javax
.crypto
.CipherInputStream
;
12 import javax
.crypto
.CipherOutputStream
;
13 import javax
.crypto
.IllegalBlockSizeException
;
14 import javax
.crypto
.SecretKey
;
15 import javax
.crypto
.spec
.IvParameterSpec
;
16 import javax
.crypto
.spec
.SecretKeySpec
;
17 import javax
.net
.ssl
.SSLException
;
19 import be
.nikiroo
.utils
.streams
.Base64InputStream
;
20 import be
.nikiroo
.utils
.streams
.Base64OutputStream
;
23 * Small utility class to do AES encryption/decryption.
25 * For the moment, it is multi-thread compatible, but beware:
27 * <li>The encrypt/decrypt calls are serialized</li>
28 * <li>The streams are independent and thus parallel</li>
31 * Do not assume it is actually secure, it is actually not.
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).
39 public class CryptUtils
{
40 static private final String AES_NAME
= "AES/CFB128/NoPadding";
42 private Cipher ecipher
;
43 private Cipher dcipher
;
44 private SecretKey key
;
47 * Small and lazy-easy way to initialize a 128 bits key with
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.
53 * <b>This is most probably not secure. Do not use if you actually care
57 * the {@link String} to use as a base for the key, can be NULL
59 public CryptUtils(String key
) {
62 } catch (InvalidKeyException e
) {
63 // We made sure that the key is correct, so nothing here
69 * Create a new instance of {@link CryptUtils} with the given 128 bytes key.
71 * The key <b>must</b> be exactly 128 bytes long.
74 * the 128 bits (32 bytes) of the key
76 * @throws InvalidKeyException
77 * if the key is not an array of 128 bytes
79 public CryptUtils(byte[] bytes32
) throws InvalidKeyException
{
84 * Wrap the given {@link InputStream} so it is transparently encrypted by
85 * the current {@link CryptUtils}.
88 * the {@link InputStream} to wrap
89 * @return the auto-encode {@link InputStream}
91 public InputStream
encrypt(InputStream in
) {
92 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
93 return new CipherInputStream(in
, ecipher
);
97 * Wrap the given {@link InputStream} so it is transparently encrypted by
98 * the current {@link CryptUtils} and encoded in base64.
101 * the {@link InputStream} to wrap
103 * @return the auto-encode {@link InputStream}
105 * @throws IOException
106 * in case of I/O error
108 public InputStream
encrypt64(InputStream in
) throws IOException
{
109 return new Base64InputStream(encrypt(in
), true);
113 * Wrap the given {@link OutputStream} so it is transparently encrypted by
114 * the current {@link CryptUtils}.
117 * the {@link OutputStream} to wrap
119 * @return the auto-encode {@link OutputStream}
121 public OutputStream
encrypt(OutputStream out
) {
122 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
123 return new CipherOutputStream(out
, ecipher
);
127 * Wrap the given {@link OutputStream} so it is transparently encrypted by
128 * the current {@link CryptUtils} and encoded in base64.
131 * the {@link OutputStream} to wrap
133 * @return the auto-encode {@link OutputStream}
135 * @throws IOException
136 * in case of I/O error
138 public OutputStream
encrypt64(OutputStream out
) throws IOException
{
139 return encrypt(new Base64OutputStream(out
, true));
143 * Wrap the given {@link OutputStream} so it is transparently decoded by the
144 * current {@link CryptUtils}.
147 * the {@link InputStream} to wrap
149 * @return the auto-decode {@link InputStream}
151 public InputStream
decrypt(InputStream in
) {
152 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
153 return new CipherInputStream(in
, dcipher
);
157 * Wrap the given {@link OutputStream} so it is transparently decoded by the
158 * current {@link CryptUtils} and decoded from base64.
161 * the {@link InputStream} to wrap
163 * @return the auto-decode {@link InputStream}
165 * @throws IOException
166 * in case of I/O error
168 public InputStream
decrypt64(InputStream in
) throws IOException
{
169 return decrypt(new Base64InputStream(in
, false));
173 * Wrap the given {@link OutputStream} so it is transparently decoded by the
174 * current {@link CryptUtils}.
177 * the {@link OutputStream} to wrap
178 * @return the auto-decode {@link OutputStream}
180 public OutputStream
decrypt(OutputStream out
) {
181 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
182 return new CipherOutputStream(out
, dcipher
);
186 * Wrap the given {@link OutputStream} so it is transparently decoded by the
187 * current {@link CryptUtils} and decoded from base64.
190 * the {@link OutputStream} to wrap
192 * @return the auto-decode {@link OutputStream}
194 * @throws IOException
195 * in case of I/O error
197 public OutputStream
decrypt64(OutputStream out
) throws IOException
{
198 return new Base64OutputStream(decrypt(out
), false);
202 * This method required an array of 128 bytes.
205 * the array, which <b>must</b> be of 128 bits (32 bytes)
207 * @throws InvalidKeyException
208 * if the key is not an array of 128 bits (32 bytes)
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
)
218 key
= new SecretKeySpec(bytes32
, "AES");
219 ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
220 dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
224 * Create a new {@link Cipher}of the given mode (see
225 * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}).
228 * the mode ({@link Cipher#ENCRYPT_MODE} or
229 * {@link Cipher#ENCRYPT_MODE})
231 * @return the new {@link Cipher}
233 private Cipher
newCipher(int mode
) {
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
);
237 Cipher cipher
= Cipher
.getInstance(AES_NAME
);
238 cipher
.init(mode
, key
, ivspec
);
240 } catch (Exception e
) {
242 throw new RuntimeException(
243 "Cannot initialize encryption sub-system", e
);
251 * the data to encrypt
253 * @return the encrypted data
255 * @throws SSLException
256 * in case of I/O error (i.e., the data is not what you assumed
259 public byte[] encrypt(byte[] data
) throws SSLException
{
260 synchronized (ecipher
) {
262 return ecipher
.doFinal(data
);
263 } catch (IllegalBlockSizeException e
) {
264 throw new SSLException(e
);
265 } catch (BadPaddingException e
) {
266 throw new SSLException(e
);
275 * the data to encrypt
277 * @return the encrypted data
279 * @throws SSLException
280 * in case of I/O error (i.e., the data is not what you assumed
283 public byte[] encrypt(String data
) throws SSLException
{
284 return encrypt(StringUtils
.getBytes(data
));
288 * Encrypt the data, then encode it into Base64.
291 * the data to encrypt
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
297 * @return the encrypted data, encoded in Base64
299 * @throws SSLException
300 * in case of I/O error (i.e., the data is not what you assumed
303 public String
encrypt64(String data
) throws SSLException
{
304 return encrypt64(StringUtils
.getBytes(data
));
308 * Encrypt the data, then encode it into Base64.
311 * the data to encrypt
313 * @return the encrypted data, encoded in Base64
315 * @throws SSLException
316 * in case of I/O error (i.e., the data is not what you assumed
319 public String
encrypt64(byte[] data
) throws SSLException
{
321 return StringUtils
.base64(encrypt(data
));
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
);
330 * Decode the data which is assumed to be encrypted with the same utilities.
333 * the encrypted data to decode
335 * @return the original, decoded data
337 * @throws SSLException
338 * in case of I/O error
340 public byte[] decrypt(byte[] data
) throws SSLException
{
341 synchronized (dcipher
) {
343 return dcipher
.doFinal(data
);
344 } catch (IllegalBlockSizeException e
) {
345 throw new SSLException(e
);
346 } catch (BadPaddingException e
) {
347 throw new SSLException(e
);
353 * Decode the data which is assumed to be encrypted with the same utilities
354 * and to be a {@link String}.
357 * the encrypted data to decode
359 * @return the original, decoded data,as a {@link String}
361 * @throws SSLException
362 * in case of I/O error
364 public String
decrypts(byte[] data
) throws SSLException
{
366 return new String(decrypt(data
), "UTF-8");
367 } catch (UnsupportedEncodingException e
) {
368 // UTF-8 is required in all confirm JVMs
375 * Decode the data which is assumed to be encrypted with the same utilities
376 * and is a Base64 encoded value.
379 * the encrypted data to decode in Base64 format
381 * TRUE to also uncompress the data from a GZIP format
382 * automatically; if set to FALSE, zipped data can be returned
384 * @return the original, decoded data
386 * @throws SSLException
387 * in case of I/O error
389 public byte[] decrypt64(String data
) throws SSLException
{
391 return decrypt(StringUtils
.unbase64(data
));
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
);
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}).
405 * the encrypted data to decode in Base64 format
407 * TRUE to also uncompress the data from a GZIP format
408 * automatically; if set to FALSE, zipped data can be returned
410 * @return the original, decoded data
412 * @throws SSLException
413 * in case of I/O error
415 public String
decrypt64s(String data
) throws SSLException
{
417 return new String(decrypt(StringUtils
.unbase64(data
)), "UTF-8");
418 } catch (UnsupportedEncodingException e
) {
419 // UTF-8 is required in all confirm JVMs
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
);
430 * This is probably <b>NOT</b> secure!
433 * some {@link String} input
435 * @return a 128 bits key computed from the given input
437 static private byte[] key2key(String input
) {
438 return StringUtils
.getMd5Hash("" + input
).getBytes();