· 8 years ago · Nov 22, 2017, 08:32 AM
1public class Encryption {
2
3 /**
4 * The Builder used to create the Encryption instance and that contains the information about
5 * encryption specifications, this instance need to be private and careful managed
6 */
7 private final Builder mBuilder;
8
9 /**
10 * The private and unique constructor, you should use the Encryption.Builder to build your own
11 * instance or get the default proving just the sensible information about encryption
12 */
13 private Encryption(Builder builder) {
14 mBuilder = builder;
15 }
16
17 /**
18 * @return an default encryption instance or {@code null} if occur some Exception, you can
19 * create yur own Encryption instance using the Encryption.Builder
20 */
21 public static Encryption getDefault(String key, String salt, byte[] iv) {
22 try {
23 return Builder.getDefaultBuilder(key, salt, iv).build();
24 } catch (NoSuchAlgorithmException e) {
25 e.printStackTrace();
26 return null;
27 }
28 }
29
30 /**
31 * Encrypt a String
32 *
33 * @param data the String to be encrypted
34 *
35 * @return the encrypted String or {@code null} if you send the data as {@code null}
36 *
37 * @throws UnsupportedEncodingException if the Builder charset name is not supported or if
38 * the Builder charset name is not supported
39 * @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
40 * or if this has no installed provider that can
41 * provide the requested by the Builder secret key
42 * type or it is {@code null}, empty or in an invalid
43 * format
44 * @throws NoSuchPaddingException if no installed provider can provide the padding
45 * scheme in the Builder digest algorithm
46 * @throws InvalidAlgorithmParameterException if the specified parameters are inappropriate for
47 * the cipher
48 * @throws InvalidKeyException if the specified key can not be used to initialize
49 * the cipher instance
50 * @throws InvalidKeySpecException if the specified key specification cannot be used
51 * to generate a secret key
52 * @throws BadPaddingException if the padding of the data does not match the
53 * padding scheme
54 * @throws IllegalBlockSizeException if the size of the resulting bytes is not a
55 * multiple of the cipher block size
56 * @throws NullPointerException if the Builder digest algorithm is {@code null} or
57 * if the specified Builder secret key type is
58 * {@code null}
59 * @throws IllegalStateException if the cipher instance is not initialized for
60 * encryption or decryption
61 */
62 public String encrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException {
63 if (data == null) return null;
64 SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
65 byte[] dataBytes = data.getBytes(mBuilder.getCharsetName());
66 Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
67 cipher.init(Cipher.ENCRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
68 return Base64.encodeToString(cipher.doFinal(dataBytes), mBuilder.getBase64Mode());
69 }
70
71 /**
72 * This is a sugar method that calls encrypt method and catch the exceptions returning
73 * {@code null} when it occurs and logging the error
74 *
75 * @param data the String to be encrypted
76 *
77 * @return the encrypted String or {@code null} if you send the data as {@code null}
78 */
79 public String encryptOrNull(String data) {
80 try {
81 return encrypt(data);
82 } catch (Exception e) {
83 e.printStackTrace();
84 return null;
85 }
86 }
87
88 /**
89 * This is a sugar method that calls encrypt method in background, it is a good idea to use this
90 * one instead the default method because encryption can take several time and with this method
91 * the process occurs in a AsyncTask, other advantage is the Callback with separated methods,
92 * one for success and other for the exception
93 *
94 * @param data the String to be encrypted
95 * @param callback the Callback to handle the results
96 */
97 public void encryptAsync(final String data, final Callback callback) {
98 if (callback == null) return;
99 new Thread(new Runnable() {
100 @Override
101 public void run() {
102 try {
103 String encrypt = encrypt(data);
104 if (encrypt == null) {
105 callback.onError(new Exception("Encrypt return null, it normally occurs when you send a null data"));
106 }
107 callback.onSuccess(encrypt);
108 } catch (Exception e) {
109 callback.onError(e);
110 }
111 }
112 }).start();
113 }
114
115 /**
116 * Decrypt a String
117 *
118 * @param data the String to be decrypted
119 *
120 * @return the decrypted String or {@code null} if you send the data as {@code null}
121 *
122 * @throws UnsupportedEncodingException if the Builder charset name is not supported or if
123 * the Builder charset name is not supported
124 * @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
125 * or if this has no installed provider that can
126 * provide the requested by the Builder secret key
127 * type or it is {@code null}, empty or in an invalid
128 * format
129 * @throws NoSuchPaddingException if no installed provider can provide the padding
130 * scheme in the Builder digest algorithm
131 * @throws InvalidAlgorithmParameterException if the specified parameters are inappropriate for
132 * the cipher
133 * @throws InvalidKeyException if the specified key can not be used to initialize
134 * the cipher instance
135 * @throws InvalidKeySpecException if the specified key specification cannot be used
136 * to generate a secret key
137 * @throws BadPaddingException if the padding of the data does not match the
138 * padding scheme
139 * @throws IllegalBlockSizeException if the size of the resulting bytes is not a
140 * multiple of the cipher block size
141 * @throws NullPointerException if the Builder digest algorithm is {@code null} or
142 * if the specified Builder secret key type is
143 * {@code null}
144 * @throws IllegalStateException if the cipher instance is not initialized for
145 * encryption or decryption
146 */
147 public String decrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
148 if (data == null) return null;
149 byte[] dataBytes = Base64.decode(data, mBuilder.getBase64Mode());
150 SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
151 Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
152 cipher.init(Cipher.DECRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
153 byte[] dataBytesDecrypted = (cipher.doFinal(dataBytes));
154 return new String(dataBytesDecrypted);
155 }
156
157 /**
158 * This is a sugar method that calls decrypt method and catch the exceptions returning
159 * {@code null} when it occurs and logging the error
160 *
161 * @param data the String to be decrypted
162 *
163 * @return the decrypted String or {@code null} if you send the data as {@code null}
164 */
165 public String decryptOrNull(String data) {
166 try {
167 return decrypt(data);
168 } catch (Exception e) {
169 e.printStackTrace();
170 return null;
171 }
172 }
173
174 /**
175 * This is a sugar method that calls decrypt method in background, it is a good idea to use this
176 * one instead the default method because decryption can take several time and with this method
177 * the process occurs in a AsyncTask, other advantage is the Callback with separated methods,
178 * one for success and other for the exception
179 *
180 * @param data the String to be decrypted
181 * @param callback the Callback to handle the results
182 */
183 public void decryptAsync(final String data, final Callback callback) {
184 if (callback == null) return;
185 new Thread(new Runnable() {
186 @Override
187 public void run() {
188 try {
189 String decrypt = decrypt(data);
190 if (decrypt == null) {
191 callback.onError(new Exception("Decrypt return null, it normally occurs when you send a null data"));
192 }
193 callback.onSuccess(decrypt);
194 } catch (Exception e) {
195 callback.onError(e);
196 }
197 }
198 }).start();
199 }
200
201 /**
202 * creates a 128bit salted aes key
203 *
204 * @param key encoded input key
205 *
206 * @return aes 128 bit salted key
207 *
208 * @throws NoSuchAlgorithmException if no installed provider that can provide the requested
209 * by the Builder secret key type
210 * @throws UnsupportedEncodingException if the Builder charset name is not supported
211 * @throws InvalidKeySpecException if the specified key specification cannot be used to
212 * generate a secret key
213 * @throws NullPointerException if the specified Builder secret key type is {@code null}
214 */
215 private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
216 SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
217 KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
218 SecretKey tmp = factory.generateSecret(spec);
219 return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
220 }
221
222 /**
223 * takes in a simple string and performs an sha1 hash
224 * that is 128 bits long...we then base64 encode it
225 * and return the char array
226 *
227 * @param key simple inputted string
228 *
229 * @return sha1 base64 encoded representation
230 *
231 * @throws UnsupportedEncodingException if the Builder charset name is not supported
232 * @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
233 * @throws NullPointerException if the Builder digest algorithm is {@code null}
234 */
235 private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
236 MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
237 messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
238 return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING).toCharArray();
239 }
240
241 /**
242 * When you encrypt or decrypt in callback mode you get noticed of result using this interface
243 */
244 public interface Callback {
245
246 /**
247 * Called when encrypt or decrypt job ends and the process was a success
248 *
249 * @param result the encrypted or decrypted String
250 */
251 void onSuccess(String result);
252
253 /**
254 * Called when encrypt or decrypt job ends and has occurred an error in the process
255 *
256 * @param exception the Exception related to the error
257 */
258 void onError(Exception exception);
259
260 }
261
262 /**
263 * This class is used to create an Encryption instance, you should provide ALL data or start
264 * with the Default Builder provided by the getDefaultBuilder method
265 */
266 public static class Builder {
267
268 private byte[] mIv;
269 private int mKeyLength;
270 private int mBase64Mode;
271 private int mIterationCount;
272 private String mSalt;
273 private String mKey;
274 private String mAlgorithm;
275 private String mKeyAlgorithm;
276 private String mCharsetName;
277 private String mSecretKeyType;
278 private String mDigestAlgorithm;
279 private String mSecureRandomAlgorithm;
280 private SecureRandom mSecureRandom;
281 private IvParameterSpec mIvParameterSpec;
282
283 /**
284 * @return an default builder with the follow defaults:
285 * the default char set is UTF-8
286 * the default base mode is Base64
287 * the Secret Key Type is the PBKDF2WithHmacSHA1
288 * the default salt is "some_salt" but can be anything
289 * the default length of key is 128
290 * the default iteration count is 65536
291 * the default algorithm is AES in CBC mode and PKCS 5 Padding
292 * the default secure random algorithm is SHA1PRNG
293 * the default message digest algorithm SHA1
294 */
295 public static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
296 return new Builder()
297 .setIv(iv)
298 .setKey(key)
299 .setSalt(salt)
300 .setKeyLength(128)
301 .setKeyAlgorithm("AES")
302 .setCharsetName("UTF8")
303 .setIterationCount(1)
304 .setDigestAlgorithm("SHA1")
305 .setBase64Mode(Base64.DEFAULT)
306 .setAlgorithm("AES/CBC/PKCS5Padding")
307 .setSecureRandomAlgorithm("SHA1PRNG")
308 .setSecretKeyType("PBKDF2WithHmacSHA1");
309 }
310
311 /**
312 * Build the Encryption with the provided information
313 *
314 * @return a new Encryption instance with provided information
315 *
316 * @throws NoSuchAlgorithmException if the specified SecureRandomAlgorithm is not available
317 * @throws NullPointerException if the SecureRandomAlgorithm is {@code null} or if the
318 * IV byte array is null
319 */
320 public Encryption build() throws NoSuchAlgorithmException {
321 setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
322 setIvParameterSpec(new IvParameterSpec(getIv()));
323 return new Encryption(this);
324 }
325
326 /**
327 * @return the charset name
328 */
329 private String getCharsetName() {
330 return mCharsetName;
331 }
332
333 /**
334 * @param charsetName the new charset name
335 *
336 * @return this instance to follow the Builder patter
337 */
338 public Builder setCharsetName(String charsetName) {
339 mCharsetName = charsetName;
340 return this;
341 }
342
343 /**
344 * @return the algorithm
345 */
346 private String getAlgorithm() {
347 return mAlgorithm;
348 }
349
350 /**
351 * @param algorithm the algorithm to be used
352 *
353 * @return this instance to follow the Builder patter
354 */
355 public Builder setAlgorithm(String algorithm) {
356 mAlgorithm = algorithm;
357 return this;
358 }
359
360 /**
361 * @return the key algorithm
362 */
363 private String getKeyAlgorithm() {
364 return mKeyAlgorithm;
365 }
366
367 /**
368 * @param keyAlgorithm the keyAlgorithm to be used in keys
369 *
370 * @return this instance to follow the Builder patter
371 */
372 public Builder setKeyAlgorithm(String keyAlgorithm) {
373 mKeyAlgorithm = keyAlgorithm;
374 return this;
375 }
376
377 /**
378 * @return the Base 64 mode
379 */
380 private int getBase64Mode() {
381 return mBase64Mode;
382 }
383
384 /**
385 * @param base64Mode set the base 64 mode
386 *
387 * @return this instance to follow the Builder patter
388 */
389 public Builder setBase64Mode(int base64Mode) {
390 mBase64Mode = base64Mode;
391 return this;
392 }
393
394 /**
395 * @return the type of aes key that will be created, on KITKAT+ the API has changed, if you
396 * are getting problems please @see <a href="http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html">http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html</a>
397 */
398 private String getSecretKeyType() {
399 return mSecretKeyType;
400 }
401
402 /**
403 * @param secretKeyType the type of AES key that will be created, on KITKAT+ the API has
404 * changed, if you are getting problems please @see <a href="http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html">http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html</a>
405 *
406 * @return this instance to follow the Builder patter
407 */
408 public Builder setSecretKeyType(String secretKeyType) {
409 mSecretKeyType = secretKeyType;
410 return this;
411 }
412
413 /**
414 * @return the value used for salting
415 */
416 private String getSalt() {
417 return mSalt;
418 }
419
420 /**
421 * @param salt the value used for salting
422 *
423 * @return this instance to follow the Builder patter
424 */
425 public Builder setSalt(String salt) {
426 mSalt = salt;
427 return this;
428 }
429
430 /**
431 * @return the key
432 */
433 private String getKey() {
434 return mKey;
435 }
436
437 /**
438 * @param key the key.
439 *
440 * @return this instance to follow the Builder patter
441 */
442 public Builder setKey(String key) {
443 mKey = key;
444 return this;
445 }
446
447 /**
448 * @return the length of key
449 */
450 private int getKeyLength() {
451 return mKeyLength;
452 }
453
454 /**
455 * @param keyLength the length of key
456 *
457 * @return this instance to follow the Builder patter
458 */
459 public Builder setKeyLength(int keyLength) {
460 mKeyLength = keyLength;
461 return this;
462 }
463
464 /**
465 * @return the number of times the password is hashed
466 */
467 private int getIterationCount() {
468 return mIterationCount;
469 }
470
471 /**
472 * @param iterationCount the number of times the password is hashed
473 *
474 * @return this instance to follow the Builder patter
475 */
476 public Builder setIterationCount(int iterationCount) {
477 mIterationCount = iterationCount;
478 return this;
479 }
480
481 /**
482 * @return the algorithm used to generate the secure random
483 */
484 private String getSecureRandomAlgorithm() {
485 return mSecureRandomAlgorithm;
486 }
487
488 /**
489 * @param secureRandomAlgorithm the algorithm to generate the secure random
490 *
491 * @return this instance to follow the Builder patter
492 */
493 public Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
494 mSecureRandomAlgorithm = secureRandomAlgorithm;
495 return this;
496 }
497
498 /**
499 * @return the IvParameterSpec bytes array
500 */
501 private byte[] getIv() {
502 return mIv;
503 }
504
505 /**
506 * @param iv the byte array to create a new IvParameterSpec
507 *
508 * @return this instance to follow the Builder patter
509 */
510 public Builder setIv(byte[] iv) {
511 mIv = iv;
512 return this;
513 }
514
515 /**
516 * @return the SecureRandom
517 */
518 private SecureRandom getSecureRandom() {
519 return mSecureRandom;
520 }
521
522 /**
523 * @param secureRandom the Secure Random
524 *
525 * @return this instance to follow the Builder patter
526 */
527 public Builder setSecureRandom(SecureRandom secureRandom) {
528 mSecureRandom = secureRandom;
529 return this;
530 }
531
532 /**
533 * @return the IvParameterSpec
534 */
535 private IvParameterSpec getIvParameterSpec() {
536 return mIvParameterSpec;
537 }
538
539 /**
540 * @param ivParameterSpec the IvParameterSpec
541 *
542 * @return this instance to follow the Builder patter
543 */
544 public Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
545 mIvParameterSpec = ivParameterSpec;
546 return this;
547 }
548
549 /**
550 * @return the message digest algorithm
551 */
552 private String getDigestAlgorithm() {
553 return mDigestAlgorithm;
554 }
555
556 /**
557 * @param digestAlgorithm the algorithm to be used to get message digest instance
558 *
559 * @return this instance to follow the Builder patter
560 */
561 public Builder setDigestAlgorithm(String digestAlgorithm) {
562 mDigestAlgorithm = digestAlgorithm;
563 return this;
564 }
565
566 }
567
568}
569
570Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
571 String encrypted = encryption.encryptOrNull(password);
572
573passwordFetched = encryption.decryptOrNull(passwordFetched);