1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.miloss.fgsms.common;
26
27
28 import java.io.ByteArrayOutputStream;
29 import java.io.DataInputStream;
30 import java.io.DataOutputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.io.UnsupportedEncodingException;
37 import java.security.GeneralSecurityException;
38 import java.security.InvalidKeyException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.Provider;
41 import java.security.SecureRandom;
42 import java.security.SecureRandomSpi;
43 import java.security.Security;
44 import java.security.spec.KeySpec;
45 import java.util.Arrays;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49
50 import javax.crypto.Cipher;
51 import javax.crypto.KeyGenerator;
52 import javax.crypto.Mac;
53 import javax.crypto.SecretKey;
54 import javax.crypto.SecretKeyFactory;
55 import javax.crypto.spec.IvParameterSpec;
56 import javax.crypto.spec.PBEKeySpec;
57 import javax.crypto.spec.SecretKeySpec;
58 import org.miloss.fgsms.common.codec.Base64;
59
60
61
62
63
64
65
66
67 public class AesCbcWithIntegrity {
68
69 private static final boolean ALLOW_BROKEN_PRNG = false;
70
71 private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
72 private static final String CIPHER = "AES";
73 private static final int AES_KEY_LENGTH_BITS = 128;
74 private static final int IV_LENGTH_BYTES = 16;
75 private static final int PBE_ITERATION_COUNT = 10000;
76 private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS;
77 private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
78
79
80
81 static final AtomicBoolean prngFixed = new AtomicBoolean(false);
82
83 private static final String HMAC_ALGORITHM = "HmacSHA256";
84 private static final int HMAC_KEY_LENGTH_BITS = 256;
85
86
87
88
89
90
91
92
93
94
95 public static boolean validateKey(final AesCbcWithIntegrity.SecretKeys key) {
96 try {
97 String src = "abcdefghijklmopqrstuvwxyz123567890!@#$%^&*()_+{}|:\">?<,";
98 CipherTextIvMac encrypt = AesCbcWithIntegrity.encrypt(src, key);
99 String decrypt = AesCbcWithIntegrity.decryptString(encrypt, key);
100
101
102 return decrypt.equals(src) && !src.equals(encrypt.toString());
103 } catch (Throwable ex) {
104 return false;
105 }
106 }
107
108
109
110
111
112
113
114
115
116 public static String keyString(SecretKeys keys) {
117 return keys.toString();
118 }
119
120
121
122
123
124
125
126
127 public static SecretKeys keys(String keysStr) throws InvalidKeyException {
128 String[] keysArr = keysStr.split(":");
129
130 if (keysArr.length != 2) {
131 System.err.println(keysStr);
132 throw new IllegalArgumentException("Cannot parse aesKey:hmacKey");
133
134 } else {
135 byte[] confidentialityKey = Base64.decodeBase64(keysArr[0]);
136 if (confidentialityKey.length != AES_KEY_LENGTH_BITS /8) {
137 throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
138 }
139 byte[] integrityKey = Base64.decodeBase64(keysArr[1]);
140 if (integrityKey.length != HMAC_KEY_LENGTH_BITS /8) {
141 throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
142 }
143
144 return new SecretKeys(
145 new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
146 new SecretKeySpec(integrityKey, HMAC_ALGORITHM));
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159 public static SecretKeys generateKey() throws GeneralSecurityException {
160 fixPrng();
161 KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
162
163
164 keyGen.init(AES_KEY_LENGTH_BITS);
165 SecretKey confidentialityKey = keyGen.generateKey();
166
167
168 byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);
169 SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
170
171 return new SecretKeys(confidentialityKey, integrityKey);
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186 public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
187 fixPrng();
188
189 KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
190 PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
191 SecretKeyFactory keyFactory = SecretKeyFactory
192 .getInstance(PBE_ALGORITHM);
193 byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
194
195
196 byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS /8);
197 byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS /8, AES_KEY_LENGTH_BITS /8 + HMAC_KEY_LENGTH_BITS /8);
198
199
200 SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
201
202
203 SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
204
205 return new SecretKeys(confidentialityKey, integrityKey);
206 }
207
208
209
210
211
212
213
214
215 public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
216 return generateKeyFromPassword(password, Base64.decodeBase64(salt));
217 }
218
219
220
221
222
223 public static byte[] generateSalt() throws GeneralSecurityException {
224 return randomBytes(PBE_SALT_LENGTH_BITS);
225 }
226
227
228
229
230
231
232
233
234 public static String saltString(byte[] salt) {
235 return Base64.encodeBase64String(salt);
236 }
237
238
239
240
241
242
243
244
245 public static byte[] generateIv() throws GeneralSecurityException {
246 return randomBytes(IV_LENGTH_BYTES);
247 }
248
249 private static byte[] randomBytes(int length) throws GeneralSecurityException {
250 fixPrng();
251 SecureRandom random = new SecureRandom();
252 byte[] b = new byte[length];
253 random.nextBytes(b);
254 return b;
255 }
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274 public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys)
275 throws UnsupportedEncodingException, GeneralSecurityException {
276 return encrypt(plaintext, secretKeys, "UTF-8");
277 }
278
279
280
281
282
283
284
285
286
287
288
289 public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys, String encoding)
290 throws UnsupportedEncodingException, GeneralSecurityException {
291 return encrypt(plaintext.getBytes(encoding), secretKeys);
292 }
293
294
295
296
297
298
299
300
301
302
303 public static CipherTextIvMac encrypt(byte[] plaintext, SecretKeys secretKeys)
304 throws GeneralSecurityException {
305 byte[] iv = generateIv();
306 Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
307 aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
308
309
310
311
312
313 iv = aesCipherForEncryption.getIV();
314 byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
315 byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
316
317 byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
318 return new CipherTextIvMac(byteCipherText, iv, integrityMac);
319 }
320
321
322
323
324
325 private static void fixPrng() {
326 if (!prngFixed.get()) {
327 synchronized (PrngFixes.class) {
328 if (!prngFixed.get()) {
329 PrngFixes.apply();
330 prngFixed.set(true);
331 }
332 }
333 }
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352 public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys, String encoding)
353 throws UnsupportedEncodingException, GeneralSecurityException {
354 return new String(decrypt(civ, secretKeys), encoding);
355 }
356
357
358
359
360
361
362
363
364
365
366
367 public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys)
368 throws UnsupportedEncodingException, GeneralSecurityException {
369 return decryptString(civ, secretKeys, "UTF-8");
370 }
371
372
373
374
375
376
377
378
379
380 public static byte[] decrypt(CipherTextIvMac civ, SecretKeys secretKeys)
381 throws GeneralSecurityException {
382
383 byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(civ.getIv(), civ.getCipherText());
384 byte[] computedMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
385 if (constantTimeEq(computedMac, civ.getMac())) {
386 Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
387 aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
388 new IvParameterSpec(civ.getIv()));
389 return aesCipherForDecryption.doFinal(civ.getCipherText());
390 } else {
391 throw new GeneralSecurityException("MAC stored in civ does not match computed MAC.");
392 }
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409 public static byte[] generateMac(byte[] byteCipherText, SecretKey integrityKey) throws NoSuchAlgorithmException, InvalidKeyException {
410
411 Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);
412 sha256_HMAC.init(integrityKey);
413 return sha256_HMAC.doFinal(byteCipherText);
414 }
415
416
417
418
419
420 public static class SecretKeys {
421 private SecretKey confidentialityKey;
422 private SecretKey integrityKey;
423
424
425
426
427
428
429 public SecretKeys(SecretKey confidentialityKeyIn, SecretKey integrityKeyIn) {
430 setConfidentialityKey(confidentialityKeyIn);
431 setIntegrityKey(integrityKeyIn);
432 }
433
434 public SecretKey getConfidentialityKey() {
435 return confidentialityKey;
436 }
437
438 public void setConfidentialityKey(SecretKey confidentialityKey) {
439 this.confidentialityKey = confidentialityKey;
440 }
441
442 public SecretKey getIntegrityKey() {
443 return integrityKey;
444 }
445
446 public void setIntegrityKey(SecretKey integrityKey) {
447 this.integrityKey = integrityKey;
448 }
449
450
451
452
453
454 @Override
455 public String toString () {
456 return Base64.encodeBase64String(getConfidentialityKey().getEncoded())
457 + ":" + Base64.encodeBase64String(getIntegrityKey().getEncoded());
458 }
459
460 @Override
461 public int hashCode() {
462 final int prime = 31;
463 int result = 1;
464 result = prime * result + confidentialityKey.hashCode();
465 result = prime * result + integrityKey.hashCode();
466 return result;
467 }
468
469 @Override
470 public boolean equals(Object obj) {
471 if (this == obj)
472 return true;
473 if (obj == null)
474 return false;
475 if (getClass() != obj.getClass())
476 return false;
477 SecretKeys other = (SecretKeys) obj;
478 if (!integrityKey.equals(other.integrityKey))
479 return false;
480 if (!confidentialityKey.equals(other.confidentialityKey))
481 return false;
482 return true;
483 }
484 }
485
486
487
488
489
490
491
492
493 public static boolean constantTimeEq(byte[] a, byte[] b) {
494 if (a.length != b.length) {
495 return false;
496 }
497 int result = 0;
498 for (int i = 0; i < a.length; i++) {
499 result |= a[i] ^ b[i];
500 }
501 return result == 0;
502 }
503
504
505
506
507 public static class CipherTextIvMac {
508 private final byte[] cipherText;
509 private final byte[] iv;
510 private final byte[] mac;
511
512 public byte[] getCipherText() {
513 return cipherText;
514 }
515
516 public byte[] getIv() {
517 return iv;
518 }
519
520 public byte[] getMac() {
521 return mac;
522 }
523
524
525
526
527
528
529
530 public CipherTextIvMac(byte[] c, byte[] i, byte[] h) {
531 cipherText = new byte[c.length];
532 System.arraycopy(c, 0, cipherText, 0, c.length);
533 iv = new byte[i.length];
534 System.arraycopy(i, 0, iv, 0, i.length);
535 mac = new byte[h.length];
536 System.arraycopy(h, 0, mac, 0, h.length);
537 }
538
539
540
541
542
543
544
545
546
547 public CipherTextIvMac(String base64IvAndCiphertext) {
548 String[] civArray = base64IvAndCiphertext.split(":");
549 if (civArray.length != 3) {
550 throw new IllegalArgumentException("Cannot parse iv:ciphertext:mac");
551 } else {
552 iv = Base64.decodeBase64(civArray[0]);
553 mac = Base64.decodeBase64(civArray[1]);
554 cipherText = Base64.decodeBase64(civArray[2]);
555 }
556 }
557
558
559
560
561
562
563
564
565 public static byte[] ivCipherConcat(byte[] iv, byte[] cipherText) {
566 byte[] combined = new byte[iv.length + cipherText.length];
567 System.arraycopy(iv, 0, combined, 0, iv.length);
568 System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
569 return combined;
570 }
571
572
573
574
575
576
577
578 @Override
579 public String toString() {
580 String ivString = Base64.encodeBase64String(iv);
581 String cipherTextString = Base64.encodeBase64String(cipherText);
582 String macString = Base64.encodeBase64String(mac);
583 return String.format(ivString + ":" + macString + ":" + cipherTextString);
584 }
585
586 @Override
587 public int hashCode() {
588 final int prime = 31;
589 int result = 1;
590 result = prime * result + Arrays.hashCode(cipherText);
591 result = prime * result + Arrays.hashCode(iv);
592 result = prime * result + Arrays.hashCode(mac);
593 return result;
594 }
595
596 @Override
597 public boolean equals(Object obj) {
598 if (this == obj)
599 return true;
600 if (obj == null)
601 return false;
602 if (getClass() != obj.getClass())
603 return false;
604 CipherTextIvMac other = (CipherTextIvMac) obj;
605 if (!Arrays.equals(cipherText, other.cipherText))
606 return false;
607 if (!Arrays.equals(iv, other.iv))
608 return false;
609 if (!Arrays.equals(mac, other.mac))
610 return false;
611 return true;
612 }
613 }
614
615
616
617
618
619
620
621
622
623 private static byte[] copyOfRange(byte[] from, int start, int end) {
624 int length = end - start;
625 byte[] result = new byte[length];
626 System.arraycopy(from, start, result, 0, length);
627 return result;
628 }
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648 public static final class PrngFixes {
649
650 private static final int VERSION_CODE_JELLY_BEAN = 16;
651 private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
652
653
654 private PrngFixes() {
655 }
656
657
658
659
660
661
662
663 public static void apply() {
664 applyOpenSSLFix();
665
666 }
667
668
669
670
671
672
673
674
675 private static void applyOpenSSLFix() throws SecurityException {
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699 }
700
701
702
703
704
705
706
707
708
709 private static void installLinuxPRNGSecureRandom() throws SecurityException {
710
711
712
713
714
715 Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
716
717
718
719
720
721
722 synchronized (java.security.Security.class) {
723 if ((secureRandomProviders == null)
724 || (secureRandomProviders.length < 1)
725 || (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
726 Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
727 }
728
729
730
731
732 SecureRandom rng1 = new SecureRandom();
733 if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
734 if (ALLOW_BROKEN_PRNG) {
735 Logger.getLogger(PrngFixes.class.getSimpleName()).warning(
736 "new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
737 return;
738 } else {
739 throw new SecurityException("new SecureRandom() backed by wrong Provider: "
740 + rng1.getProvider().getClass());
741 }
742 }
743
744 SecureRandom rng2 = null;
745 try {
746 rng2 = SecureRandom.getInstance("SHA1PRNG");
747 } catch (NoSuchAlgorithmException e) {
748 if (ALLOW_BROKEN_PRNG) {
749 Logger.getLogger(PrngFixes.class.getSimpleName()).warning("SHA1PRNG not available"+ e.getMessage());
750 return;
751 } else {
752 new SecurityException("SHA1PRNG not available", e);
753 }
754 }
755 if (rng2!=null && !rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
756 if (ALLOW_BROKEN_PRNG) {
757 Logger.getLogger(PrngFixes.class.getSimpleName()).warning(
758 "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
759 + rng2.getProvider().getClass());
760 return;
761 } else {
762 throw new SecurityException(
763 "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
764 + rng2.getProvider().getClass());
765 }
766 }
767 }
768 }
769
770
771
772
773
774 private static class LinuxPRNGSecureRandomProvider extends Provider {
775
776 public LinuxPRNGSecureRandomProvider() {
777 super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
778 + " /dev/urandom");
779
780
781
782
783 put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
784 put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
785 }
786 }
787
788
789
790
791
792 public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
793
794
795
796
797
798
799
800
801
802
803
804
805
806 private static final File URANDOM_FILE = new File("/dev/urandom");
807
808 private static final Object sLock = new Object();
809
810
811
812
813
814
815
816 private static DataInputStream sUrandomIn;
817
818
819
820
821
822
823
824 private static OutputStream sUrandomOut;
825
826
827
828
829
830
831 private boolean mSeeded;
832
833 @Override
834 protected void engineSetSeed(byte[] bytes) {
835 OutputStream out=null;
836 try {
837
838 synchronized (sLock) {
839 out = getUrandomOutputStream();
840 }
841 out.write(bytes);
842 out.flush();
843 } catch (IOException e) {
844
845
846 Logger.getLogger(PrngFixes.class.getSimpleName()).warning( "Failed to mix seed into "
847 + URANDOM_FILE);
848 } finally {
849 mSeeded = true;
850 if (out!=null)
851 try{
852 out.close();}catch (Exception ex){}
853 }
854 }
855
856 @Override
857 protected void engineNextBytes(byte[] bytes) {
858 if (!mSeeded) {
859
860 engineSetSeed(generateSeed());
861 }
862
863 try {
864 DataInputStream in;
865 synchronized (sLock) {
866 in = getUrandomInputStream();
867 }
868 synchronized (in) {
869 in.readFully(bytes);
870 }
871 } catch (IOException e) {
872 throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
873 }
874 }
875
876 @Override
877 protected byte[] engineGenerateSeed(int size) {
878 byte[] seed = new byte[size];
879 engineNextBytes(seed);
880 return seed;
881 }
882
883 private DataInputStream getUrandomInputStream() {
884 synchronized (sLock) {
885 if (sUrandomIn == null) {
886
887
888
889
890 try {
891 sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
892 } catch (IOException e) {
893 throw new SecurityException("Failed to open " + URANDOM_FILE
894 + " for reading", e);
895 }
896 }
897 return sUrandomIn;
898 }
899 }
900
901 private OutputStream getUrandomOutputStream() throws IOException {
902 synchronized (sLock) {
903 if (sUrandomOut == null) {
904 sUrandomOut = new FileOutputStream(URANDOM_FILE);
905 }
906 return sUrandomOut;
907 }
908 }
909 }
910
911
912
913
914
915 private static byte[] generateSeed() {
916 try {
917 ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
918 DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
919 seedBufferOut.writeLong(System.currentTimeMillis());
920 seedBufferOut.writeLong(System.nanoTime());
921 seedBufferOut.close();
922 return seedBuffer.toByteArray();
923 } catch (IOException e) {
924 throw new SecurityException("Failed to generate seed", e);
925 }
926 }
927
928 }
929 }