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
.spec
.IvParameterSpec
;
15 import javax
.crypto
.spec
.SecretKeySpec
;
16 import javax
.net
.ssl
.SSLException
;
18 import be
.nikiroo
.utils
.streams
.Base64InputStream
;
19 import be
.nikiroo
.utils
.streams
.Base64OutputStream
;
22 * Small utility class to do AES encryption/decryption.
24 * For the moment, it is multi-thread compatible, but beware:
26 * <li>The encrypt/decrypt calls are serialized</li>
27 * <li>The streams are independent and thus parallel</li>
30 * Do not assume it is actually secure, it is actually not.
32 * It just here to offer a more-or-less protected exchange of data because
33 * anonymous and self-signed certificates backed SSL is against Google wishes
34 * (so, don't even try, they own Internet).
38 public class CryptUtils
{
39 static private final String AES_NAME
= "AES/CFB128/NoPadding";
41 private Cipher ecipher
;
42 private Cipher dcipher
;
43 private byte[] bytes32
;
46 * Small and lazy-easy way to initialize a 128 bits key with
49 * <b>Some</b> part of the key will be used to generate a 128 bits key and
50 * initialize the {@link CryptUtils}; even NULL will generate something.
52 * <b>This is most probably not secure. Do not use if you actually care
56 * the {@link String} to use as a base for the key, can be NULL
58 public CryptUtils(String key
) {
61 } catch (InvalidKeyException e
) {
62 // We made sure that the key is correct, so nothing here
68 * Create a new instance of {@link CryptUtils} with the given 128 bytes key.
70 * The key <b>must</b> be exactly 128 bytes long.
73 * the 128 bits (32 bytes) of the key
75 * @throws InvalidKeyException
76 * if the key is not an array of 128 bytes
78 public CryptUtils(byte[] bytes32
) throws InvalidKeyException
{
83 * Wrap the given {@link InputStream} so it is transparently encrypted by
84 * the current {@link CryptUtils}.
87 * the {@link InputStream} to wrap
88 * @return the auto-encode {@link InputStream}
90 public InputStream
encrypt(InputStream in
) {
91 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
92 return new CipherInputStream(in
, ecipher
);
96 * Wrap the given {@link InputStream} so it is transparently encrypted by
97 * the current {@link CryptUtils} and encoded in base64.
100 * the {@link InputStream} to wrap
102 * @return the auto-encode {@link InputStream}
104 * @throws IOException
105 * in case of I/O error
107 public InputStream
encrypt64(InputStream in
) throws IOException
{
108 return new Base64InputStream(encrypt(in
), true);
112 * Wrap the given {@link OutputStream} so it is transparently encrypted by
113 * the current {@link CryptUtils}.
116 * the {@link OutputStream} to wrap
118 * @return the auto-encode {@link OutputStream}
120 public OutputStream
encrypt(OutputStream out
) {
121 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
122 return new CipherOutputStream(out
, ecipher
);
126 * Wrap the given {@link OutputStream} so it is transparently encrypted by
127 * the current {@link CryptUtils} and encoded in base64.
130 * the {@link OutputStream} to wrap
132 * @return the auto-encode {@link OutputStream}
134 * @throws IOException
135 * in case of I/O error
137 public OutputStream
encrypt64(OutputStream out
) throws IOException
{
138 return encrypt(new Base64OutputStream(out
, true));
142 * Wrap the given {@link OutputStream} so it is transparently decoded by the
143 * current {@link CryptUtils}.
146 * the {@link InputStream} to wrap
148 * @return the auto-decode {@link InputStream}
150 public InputStream
decrypt(InputStream in
) {
151 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
152 return new CipherInputStream(in
, dcipher
);
156 * Wrap the given {@link OutputStream} so it is transparently decoded by the
157 * current {@link CryptUtils} and decoded from base64.
160 * the {@link InputStream} to wrap
162 * @return the auto-decode {@link InputStream}
164 * @throws IOException
165 * in case of I/O error
167 public InputStream
decrypt64(InputStream in
) throws IOException
{
168 return decrypt(new Base64InputStream(in
, false));
172 * Wrap the given {@link OutputStream} so it is transparently decoded by the
173 * current {@link CryptUtils}.
176 * the {@link OutputStream} to wrap
177 * @return the auto-decode {@link OutputStream}
179 public OutputStream
decrypt(OutputStream out
) {
180 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
181 return new CipherOutputStream(out
, dcipher
);
185 * Wrap the given {@link OutputStream} so it is transparently decoded by the
186 * current {@link CryptUtils} and decoded from base64.
189 * the {@link OutputStream} to wrap
191 * @return the auto-decode {@link OutputStream}
193 * @throws IOException
194 * in case of I/O error
196 public OutputStream
decrypt64(OutputStream out
) throws IOException
{
197 return new Base64OutputStream(decrypt(out
), false);
201 * This method required an array of 128 bytes.
204 * the array, which <b>must</b> be of 128 bits (32 bytes)
206 * @throws InvalidKeyException
207 * if the key is not an array of 128 bits (32 bytes)
209 private void init(byte[] bytes32
) throws InvalidKeyException
{
210 if (bytes32
== null || bytes32
.length
!= 32) {
211 throw new InvalidKeyException(
212 "The size of the key must be of 128 bits (32 bytes), it is: "
213 + (bytes32
== null ?
"null" : "" + bytes32
.length
)
217 this.bytes32
= bytes32
;
218 this.ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
219 this.dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
223 * Create a new {@link Cipher}of the given mode (see
224 * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}).
227 * the mode ({@link Cipher#ENCRYPT_MODE} or
228 * {@link Cipher#ENCRYPT_MODE})
230 * @return the new {@link Cipher}
232 private Cipher
newCipher(int mode
) {
234 // bytes32 = 32 bytes, 32 > 16
235 byte[] iv
= new byte[16];
236 for (int i
= 0; i
< iv
.length
; i
++) {
239 IvParameterSpec ivspec
= new IvParameterSpec(iv
);
240 Cipher cipher
= Cipher
.getInstance(AES_NAME
);
241 cipher
.init(mode
, new SecretKeySpec(bytes32
, "AES"), ivspec
);
243 } catch (Exception e
) {
245 throw new RuntimeException(
246 "Cannot initialize encryption sub-system", e
);
254 * the data to encrypt
256 * @return the encrypted data
258 * @throws SSLException
259 * in case of I/O error (i.e., the data is not what you assumed
262 public byte[] encrypt(byte[] data
) throws SSLException
{
263 synchronized (ecipher
) {
265 return ecipher
.doFinal(data
);
266 } catch (IllegalBlockSizeException e
) {
267 throw new SSLException(e
);
268 } catch (BadPaddingException e
) {
269 throw new SSLException(e
);
278 * the data to encrypt
280 * @return the encrypted data
282 * @throws SSLException
283 * in case of I/O error (i.e., the data is not what you assumed
286 public byte[] encrypt(String data
) throws SSLException
{
287 return encrypt(StringUtils
.getBytes(data
));
291 * Encrypt the data, then encode it into Base64.
294 * the data to encrypt
296 * TRUE to also compress the data in GZIP format; remember that
297 * compressed and not-compressed content are different; you need
298 * to know which is which when decoding
300 * @return the encrypted data, encoded in Base64
302 * @throws SSLException
303 * in case of I/O error (i.e., the data is not what you assumed
306 public String
encrypt64(String data
) throws SSLException
{
307 return encrypt64(StringUtils
.getBytes(data
));
311 * Encrypt the data, then encode it into Base64.
314 * the data to encrypt
316 * @return the encrypted data, encoded in Base64
318 * @throws SSLException
319 * in case of I/O error (i.e., the data is not what you assumed
322 public String
encrypt64(byte[] data
) throws SSLException
{
324 return StringUtils
.base64(encrypt(data
));
325 } catch (IOException e
) {
326 // not exactly true, but we consider here that this error is a crypt
327 // error, not a normal I/O error
328 throw new SSLException(e
);
333 * Decode the data which is assumed to be encrypted with the same utilities.
336 * the encrypted data to decode
338 * @return the original, decoded data
340 * @throws SSLException
341 * in case of I/O error
343 public byte[] decrypt(byte[] data
) throws SSLException
{
344 synchronized (dcipher
) {
346 return dcipher
.doFinal(data
);
347 } catch (IllegalBlockSizeException e
) {
348 throw new SSLException(e
);
349 } catch (BadPaddingException e
) {
350 throw new SSLException(e
);
356 * Decode the data which is assumed to be encrypted with the same utilities
357 * and to be a {@link String}.
360 * the encrypted data to decode
362 * @return the original, decoded data,as a {@link String}
364 * @throws SSLException
365 * in case of I/O error
367 public String
decrypts(byte[] data
) throws SSLException
{
369 return new String(decrypt(data
), "UTF-8");
370 } catch (UnsupportedEncodingException e
) {
371 // UTF-8 is required in all confirm JVMs
378 * Decode the data which is assumed to be encrypted with the same utilities
379 * and is a Base64 encoded value.
382 * the encrypted data to decode in Base64 format
384 * TRUE to also uncompress the data from a GZIP format
385 * automatically; if set to FALSE, zipped data can be returned
387 * @return the original, decoded data
389 * @throws SSLException
390 * in case of I/O error
392 public byte[] decrypt64(String data
) throws SSLException
{
394 return decrypt(StringUtils
.unbase64(data
));
395 } catch (IOException e
) {
396 // not exactly true, but we consider here that this error is a crypt
397 // error, not a normal I/O error
398 throw new SSLException(e
);
403 * Decode the data which is assumed to be encrypted with the same utilities
404 * and is a Base64 encoded value, then convert it into a String (this method
405 * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}).
408 * the encrypted data to decode in Base64 format
410 * TRUE to also uncompress the data from a GZIP format
411 * automatically; if set to FALSE, zipped data can be returned
413 * @return the original, decoded data
415 * @throws SSLException
416 * in case of I/O error
418 public String
decrypt64s(String data
) throws SSLException
{
420 return new String(decrypt(StringUtils
.unbase64(data
)), "UTF-8");
421 } catch (UnsupportedEncodingException e
) {
422 // UTF-8 is required in all confirm JVMs
425 } catch (IOException e
) {
426 // not exactly true, but we consider here that this error is a crypt
427 // error, not a normal I/O error
428 throw new SSLException(e
);
433 * This is probably <b>NOT</b> secure!
436 * some {@link String} input
438 * @return a 128 bits key computed from the given input
440 static private byte[] key2key(String input
) {
441 return StringUtils
.getMd5Hash("" + input
).getBytes();