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 * 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 secure; it just here to offer a more-or-less protected
31 * exchange of data because anonymous and self-signed certificates backed SSL is
32 * against Google wishes, and I need Android support.
36 public class CryptUtils
{
37 static private final String AES_NAME
= "AES/CFB128/NoPadding";
39 private Cipher ecipher
;
40 private Cipher dcipher
;
41 private byte[] bytes32
;
44 * Small and lazy-easy way to initialize a 128 bits key with
47 * <b>Some</b> part of the key will be used to generate a 128 bits key and
48 * initialize the {@link CryptUtils}; even NULL will generate something.
50 * <b>This is most probably not secure. Do not use if you actually care
54 * the {@link String} to use as a base for the key, can be NULL
56 public CryptUtils(String key
) {
59 } catch (InvalidKeyException e
) {
60 // We made sure that the key is correct, so nothing here
66 * Create a new instance of {@link CryptUtils} with the given 128 bits key.
68 * The key <b>must</b> be exactly 128 bits long.
71 * the 128 bits (32 bytes) of the key
73 * @throws InvalidKeyException
74 * if the key is not an array of 128 bits
76 public CryptUtils(byte[] bytes32
) throws InvalidKeyException
{
81 * Wrap the given {@link InputStream} so it is transparently encrypted by
82 * the current {@link CryptUtils}.
85 * the {@link InputStream} to wrap
86 * @return the auto-encode {@link InputStream}
88 public InputStream
encrypt(InputStream in
) {
89 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
90 return new CipherInputStream(in
, ecipher
);
94 * Wrap the given {@link InputStream} so it is transparently encrypted by
95 * the current {@link CryptUtils} and encoded in base64.
98 * the {@link InputStream} to wrap
100 * @return the auto-encode {@link InputStream}
102 * @throws IOException
103 * in case of I/O error
105 public InputStream
encrypt64(InputStream in
) throws IOException
{
106 return new Base64InputStream(encrypt(in
), true);
110 * Wrap the given {@link OutputStream} so it is transparently encrypted by
111 * the current {@link CryptUtils}.
114 * the {@link OutputStream} to wrap
116 * @return the auto-encode {@link OutputStream}
118 public OutputStream
encrypt(OutputStream out
) {
119 Cipher ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
120 return new CipherOutputStream(out
, ecipher
);
124 * Wrap the given {@link OutputStream} so it is transparently encrypted by
125 * the current {@link CryptUtils} and encoded in base64.
128 * the {@link OutputStream} to wrap
130 * @return the auto-encode {@link OutputStream}
132 * @throws IOException
133 * in case of I/O error
135 public OutputStream
encrypt64(OutputStream out
) throws IOException
{
136 return encrypt(new Base64OutputStream(out
, true));
140 * Wrap the given {@link OutputStream} so it is transparently decoded by the
141 * current {@link CryptUtils}.
144 * the {@link InputStream} to wrap
146 * @return the auto-decode {@link InputStream}
148 public InputStream
decrypt(InputStream in
) {
149 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
150 return new CipherInputStream(in
, dcipher
);
154 * Wrap the given {@link OutputStream} so it is transparently decoded by the
155 * current {@link CryptUtils} and decoded from base64.
158 * the {@link InputStream} to wrap
160 * @return the auto-decode {@link InputStream}
162 * @throws IOException
163 * in case of I/O error
165 public InputStream
decrypt64(InputStream in
) throws IOException
{
166 return decrypt(new Base64InputStream(in
, false));
170 * Wrap the given {@link OutputStream} so it is transparently decoded by the
171 * current {@link CryptUtils}.
174 * the {@link OutputStream} to wrap
175 * @return the auto-decode {@link OutputStream}
177 public OutputStream
decrypt(OutputStream out
) {
178 Cipher dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
179 return new CipherOutputStream(out
, dcipher
);
183 * Wrap the given {@link OutputStream} so it is transparently decoded by the
184 * current {@link CryptUtils} and decoded from base64.
187 * the {@link OutputStream} to wrap
189 * @return the auto-decode {@link OutputStream}
191 * @throws IOException
192 * in case of I/O error
194 public OutputStream
decrypt64(OutputStream out
) throws IOException
{
195 return new Base64OutputStream(decrypt(out
), false);
199 * This method required an array of 128 bits.
202 * the array, which <b>must</b> be of 128 bits (32 bytes)
204 * @throws InvalidKeyException
205 * if the key is not an array of 128 bits (32 bytes)
207 private void init(byte[] bytes32
) throws InvalidKeyException
{
208 if (bytes32
== null || bytes32
.length
!= 32) {
209 throw new InvalidKeyException(
210 "The size of the key must be of 128 bits (32 bytes), it is: "
211 + (bytes32
== null ?
"null" : "" + bytes32
.length
)
215 this.bytes32
= bytes32
;
216 this.ecipher
= newCipher(Cipher
.ENCRYPT_MODE
);
217 this.dcipher
= newCipher(Cipher
.DECRYPT_MODE
);
221 * Create a new {@link Cipher}of the given mode (see
222 * {@link Cipher#ENCRYPT_MODE} and {@link Cipher#ENCRYPT_MODE}).
225 * the mode ({@link Cipher#ENCRYPT_MODE} or
226 * {@link Cipher#ENCRYPT_MODE})
228 * @return the new {@link Cipher}
230 private Cipher
newCipher(int mode
) {
232 // bytes32 = 32 bytes, 32 > 16
233 byte[] iv
= new byte[16];
234 for (int i
= 0; i
< iv
.length
; i
++) {
237 IvParameterSpec ivspec
= new IvParameterSpec(iv
);
238 Cipher cipher
= Cipher
.getInstance(AES_NAME
);
239 cipher
.init(mode
, new SecretKeySpec(bytes32
, "AES"), ivspec
);
241 } catch (Exception e
) {
243 throw new RuntimeException(
244 "Cannot initialize encryption sub-system", e
);
252 * the data to encrypt
254 * @return the encrypted data
256 * @throws SSLException
257 * in case of I/O error (i.e., the data is not what you assumed
260 public byte[] encrypt(byte[] data
) throws SSLException
{
261 synchronized (ecipher
) {
263 return ecipher
.doFinal(data
);
264 } catch (IllegalBlockSizeException e
) {
265 throw new SSLException(e
);
266 } catch (BadPaddingException e
) {
267 throw new SSLException(e
);
276 * the data to encrypt
278 * @return the encrypted data
280 * @throws SSLException
281 * in case of I/O error (i.e., the data is not what you assumed
284 public byte[] encrypt(String data
) throws SSLException
{
285 return encrypt(StringUtils
.getBytes(data
));
289 * Encrypt the data, then encode it into Base64.
292 * the data to encrypt
294 * TRUE to also compress the data in GZIP format; remember that
295 * compressed and not-compressed content are different; you need
296 * to know which is which when decoding
298 * @return the encrypted data, encoded in Base64
300 * @throws SSLException
301 * in case of I/O error (i.e., the data is not what you assumed
304 public String
encrypt64(String data
) throws SSLException
{
305 return encrypt64(StringUtils
.getBytes(data
));
309 * Encrypt the data, then encode it into Base64.
312 * the data to encrypt
314 * @return the encrypted data, encoded in Base64
316 * @throws SSLException
317 * in case of I/O error (i.e., the data is not what you assumed
320 public String
encrypt64(byte[] data
) throws SSLException
{
322 return StringUtils
.base64(encrypt(data
));
323 } catch (IOException e
) {
324 // not exactly true, but we consider here that this error is a crypt
325 // error, not a normal I/O error
326 throw new SSLException(e
);
331 * Decode the data which is assumed to be encrypted with the same utilities.
334 * the encrypted data to decode
336 * @return the original, decoded data
338 * @throws SSLException
339 * in case of I/O error
341 public byte[] decrypt(byte[] data
) throws SSLException
{
342 synchronized (dcipher
) {
344 return dcipher
.doFinal(data
);
345 } catch (IllegalBlockSizeException e
) {
346 throw new SSLException(e
);
347 } catch (BadPaddingException e
) {
348 throw new SSLException(e
);
354 * Decode the data which is assumed to be encrypted with the same utilities
355 * and to be a {@link String}.
358 * the encrypted data to decode
360 * @return the original, decoded data,as a {@link String}
362 * @throws SSLException
363 * in case of I/O error
365 public String
decrypts(byte[] data
) throws SSLException
{
367 return new String(decrypt(data
), "UTF-8");
368 } catch (UnsupportedEncodingException e
) {
369 // UTF-8 is required in all conform JVMs
376 * Decode the data which is assumed to be encrypted with the same utilities
377 * and is a Base64 encoded value.
380 * the encrypted data to decode in Base64 format
382 * TRUE to also uncompress the data from a GZIP format
383 * automatically; if set to FALSE, zipped data can be returned
385 * @return the original, decoded data
387 * @throws SSLException
388 * in case of I/O error
390 public byte[] decrypt64(String data
) throws SSLException
{
392 return decrypt(StringUtils
.unbase64(data
));
393 } catch (IOException e
) {
394 // not exactly true, but we consider here that this error is a crypt
395 // error, not a normal I/O error
396 throw new SSLException(e
);
401 * Decode the data which is assumed to be encrypted with the same utilities
402 * and is a Base64 encoded value, then convert it into a String (this method
403 * assumes the data <b>was</b> indeed a UTF-8 encoded {@link String}).
406 * the encrypted data to decode in Base64 format
408 * TRUE to also uncompress the data from a GZIP format
409 * automatically; if set to FALSE, zipped data can be returned
411 * @return the original, decoded data
413 * @throws SSLException
414 * in case of I/O error
416 public String
decrypt64s(String data
) throws SSLException
{
418 return new String(decrypt(StringUtils
.unbase64(data
)), "UTF-8");
419 } catch (UnsupportedEncodingException e
) {
420 // UTF-8 is required in all conform JVMs
423 } catch (IOException e
) {
424 // not exactly true, but we consider here that this error is a crypt
425 // error, not a normal I/O error
426 throw new SSLException(e
);
431 * This is probably <b>NOT</b> secure!
434 * some {@link String} input
436 * @return a 128 bits key computed from the given input
438 static private byte[] key2key(String input
) {
439 return StringUtils
.getMd5Hash("" + input
).getBytes();