View Javadoc
1   /*
2    * Copyright (c) 2014-2015 Tozny LLC
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   * THE SOFTWARE.
21   *
22   * Created by Isaac Potoczny-Jones on 11/12/14.
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   * Simple library for the "right" defaults for AES key generation, encryption,
64   * and decryption using 128-bit AES, CBC, PKCS5 padding, and a random 16-byte IV
65   * with SHA1PRNG. Integrity with HmacSHA256.
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; // same size as key output
77      private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
78   
79  
80      //default for testing
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      // If the PRNG fix would not succeed for some reason, we normally will throw an exception.
87      // If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
88     
89        /**
90       * return true is the supplied key is a valid aes key
91       *
92       * @param key
93       * @return
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      * Converts the given AES/HMAC keys into a base64 encoded string suitable for
111      * storage. Sister function of keys.
112      *
113      * @param keys The combined aes and hmac keys
114      * @return a base 64 encoded AES string & hmac key as base64(aesKey) : base64(hmacKey)
115      */
116     public static String keyString(SecretKeys keys) {
117         return keys.toString();
118     }
119 
120     /**
121      * An aes key derived from a base64 encoded key. This does not generate the
122      * key. It's not random or a PBE key.
123      *
124      * @param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
125      * @return an AES & HMAC key set suitable for other functions.
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      * A function that generates random AES & HMAC keys and prints out exceptions but
152      * doesn't throw them since none should be encountered. If they are
153      * encountered, the return value is null.
154      *
155      * @return The AES & HMAC keys.
156      * @throws GeneralSecurityException if AES is not implemented on this system,
157      *                                  or a suitable RNG is not available
158      */
159     public static SecretKeys generateKey() throws GeneralSecurityException {
160         fixPrng();
161         KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
162         // No need to provide a SecureRandom or set a seed since that will
163         // happen automatically.
164         keyGen.init(AES_KEY_LENGTH_BITS);
165         SecretKey confidentialityKey = keyGen.generateKey();
166 
167         //Now make the HMAC key
168         byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);//to get bytes
169         SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
170 
171         return new SecretKeys(confidentialityKey, integrityKey);
172     }
173     
174     
175 
176     /**
177      * A function that generates password-based AES & HMAC keys. It prints out exceptions but
178      * doesn't throw them since none should be encountered. If they are
179      * encountered, the return value is null.
180      *
181      * @param password The password to derive the keys from.
182      * @return The AES & HMAC keys.
183      * @throws GeneralSecurityException if AES is not implemented on this system,
184      *                                  or a suitable RNG is not available
185      */
186     public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
187         fixPrng();
188         //Get enough random bytes for both the AES key and the HMAC key:
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         // Split the random bytes into two parts:
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         //Generate the AES key
200         SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
201 
202         //Generate the HMAC key
203         SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
204 
205         return new SecretKeys(confidentialityKey, integrityKey);
206     }
207 
208     /**
209      * A function that generates password-based AES & HMAC keys. See generateKeyFromPassword.
210      * @param password The password to derive the AES/HMAC keys from
211      * @param salt A string version of the salt; base64 encoded.
212      * @return The AES & HMAC keys.
213      * @throws GeneralSecurityException
214      */
215     public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
216         return generateKeyFromPassword(password, Base64.decodeBase64(salt));
217     }
218 
219     /**
220      * Generates a random salt.
221      * @return The random salt suitable for generateKeyFromPassword.
222      */
223     public static byte[] generateSalt() throws GeneralSecurityException {
224         return randomBytes(PBE_SALT_LENGTH_BITS);
225     }
226 
227     /**
228      * Converts the given salt into a base64 encoded string suitable for
229      * storage.
230      *
231      * @param salt
232      * @return a base 64 encoded salt string suitable to pass into generateKeyFromPassword.
233      */
234     public static String saltString(byte[] salt) {
235         return Base64.encodeBase64String(salt);
236     }
237 
238 
239     /**
240      * Creates a random Initialization Vector (IV) of IV_LENGTH_BYTES.
241      *
242      * @return The byte array of this IV
243      * @throws GeneralSecurityException if a suitable RNG is not available
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      * Encryption
260      * -----------------------------------------------------------------
261      */
262 
263     /**
264      * Generates a random IV and encrypts this plain text with the given key. Then attaches
265      * a hashed MAC, which is contained in the CipherTextIvMac class.
266      *
267      * @param plaintext The text that will be encrypted, which
268      *                  will be serialized with UTF-8
269      * @param secretKeys The AES & HMAC keys with which to encrypt
270      * @return a tuple of the IV, ciphertext, mac
271      * @throws GeneralSecurityException if AES is not implemented on this system
272      * @throws UnsupportedEncodingException if UTF-8 is not supported in this system
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      * Generates a random IV and encrypts this plain text with the given key. Then attaches
281      * a hashed MAC, which is contained in the CipherTextIvMac class.
282      *
283      * @param plaintext The bytes that will be encrypted
284      * @param secretKeys The AES & HMAC keys with which to encrypt
285      * @return a tuple of the IV, ciphertext, mac
286      * @throws GeneralSecurityException if AES is not implemented on this system
287      * @throws UnsupportedEncodingException if the specified encoding is invalid
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      * Generates a random IV and encrypts this plain text with the given key. Then attaches
296      * a hashed MAC, which is contained in the CipherTextIvMac class.
297      *
298      * @param plaintext The text that will be encrypted
299      * @param secretKeys The combined AES & HMAC keys with which to encrypt
300      * @return a tuple of the IV, ciphertext, mac
301      * @throws GeneralSecurityException if AES is not implemented on this system
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          * Now we get back the IV that will actually be used. Some Android
311          * versions do funny stuff w/ the IV, so this is to work around bugs:
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      * Ensures that the PRNG is fixed. Should be used before generating any keys.
323      * Will only run once, and every subsequent call should return immediately.
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      * Decryption
339      * -----------------------------------------------------------------
340      */
341 
342     /**
343      * AES CBC decrypt.
344      *
345      * @param civ The cipher text, IV, and mac
346      * @param secretKeys The AES & HMAC keys
347      * @param encoding The string encoding to use to decode the bytes after decryption
348      * @return A string derived from the decrypted bytes (not base64 encoded)
349      * @throws GeneralSecurityException if AES is not implemented on this system
350      * @throws UnsupportedEncodingException if the encoding is unsupported
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      * AES CBC decrypt.
359      *
360      * @param civ The cipher text, IV, and mac
361      * @param secretKeys The AES & HMAC keys
362      * @return A string derived from the decrypted bytes, which are interpreted
363      *         as a UTF-8 String
364      * @throws GeneralSecurityException if AES is not implemented on this system
365      * @throws UnsupportedEncodingException if UTF-8 is not supported
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      * AES CBC decrypt.
374      *
375      * @param civ the cipher text, iv, and mac
376      * @param secretKeys the AES & HMAC keys
377      * @return The raw decrypted bytes
378      * @throws GeneralSecurityException if MACs don't match or AES is not implemented
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      * Helper Code
398      * -----------------------------------------------------------------
399      */
400 
401     /**
402      * Generate the mac based on HMAC_ALGORITHM
403      * @param integrityKey The key used for hmac
404      * @param byteCipherText the cipher text
405      * @return A byte array of the HMAC for the given key & ciphertext
406      * @throws NoSuchAlgorithmException
407      * @throws InvalidKeyException
408      */
409     public static byte[] generateMac(byte[] byteCipherText, SecretKey integrityKey) throws NoSuchAlgorithmException, InvalidKeyException {
410         //Now compute the mac for later integrity checking
411         Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);
412         sha256_HMAC.init(integrityKey);
413         return sha256_HMAC.doFinal(byteCipherText);
414     }
415     /**
416      * Holder class that has both the secret AES key for encryption (confidentiality)
417      * and the secret HMAC key for integrity.
418      */
419 
420     public static class SecretKeys {
421         private SecretKey confidentialityKey;
422         private SecretKey integrityKey;
423 
424         /**
425          * Construct the secret keys container.
426          * @param confidentialityKeyIn The AES key
427          * @param integrityKeyIn the HMAC key
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          * Encodes the two keys as a string
452          * @return base64(confidentialityKey):base64(integrityKey)
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      * Simple constant-time equality of two byte arrays. Used for security to avoid timing attacks.
489      * @param a
490      * @param b
491      * @return true iff the arrays are exactly equal.
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      * Holder class that allows us to bundle ciphertext and IV together.
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          * Construct a new bundle of ciphertext and IV.
526          * @param c The ciphertext
527          * @param i The IV
528          * @param h The mac
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          * Constructs a new bundle of ciphertext and IV from a string of the
541          * format <code>base64(iv):base64(ciphertext)</code>.
542          *
543          * @param base64IvAndCiphertext A string of the format
544          *            <code>iv:ciphertext</code> The IV and ciphertext must each
545          *            be base64-encoded.
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          * Concatinate the IV to the cipherText using array copy.
560          * This is used e.g. before computing mac.
561          * @param iv The IV to prepend
562          * @param cipherText the cipherText to append
563          * @return iv:cipherText, a new byte array.
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          * Encodes this ciphertext, IV, mac as a string.
574          *
575          * @return base64(iv) : base64(mac) : base64(ciphertext).
576          * The iv and mac go first because they're fixed length.
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      * Copy the elements from the start to the end
617      *
618      * @param from  the source
619      * @param start the start index to copy
620      * @param end   the end index to finish
621      * @return the new buffer
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      * Fixes for the RNG as per
632      * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
633      *
634      * This software is provided 'as-is', without any express or implied
635      * warranty. In no event will Google be held liable for any damages arising
636      * from the use of this software.
637      *
638      * Permission is granted to anyone to use this software for any purpose,
639      * including commercial applications, and to alter it and redistribute it
640      * freely, as long as the origin is not misrepresented.
641      *
642      * Fixes for the output of the default PRNG having low entropy.
643      *
644      * The fixes need to be applied via {@link #apply()} before any use of Java
645      * Cryptography Architecture primitives. A good place to invoke them is in
646      * the application's {@code onCreate}.
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         /** Hidden constructor to prevent instantiation. */
654         private PrngFixes() {
655         }
656 
657         /**
658          * Applies all fixes.
659          *
660          * @throws SecurityException if a fix is needed but could not be
661          *             applied.
662          */
663         public static void apply() {
664             applyOpenSSLFix();
665             //installLinuxPRNGSecureRandom();
666         }
667 
668         /**
669          * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
670          * the fix is not needed.
671          *
672          * @throws SecurityException if the fix is needed but could not be
673          *             applied.
674          */
675         private static void applyOpenSSLFix() throws SecurityException {
676 
677 /*
678             try {
679                 // Mix in the device- and invocation-specific seed.
680                 Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
681                         .getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
682 
683                 // Mix output of Linux PRNG into OpenSSL's PRNG
684                 int bytesRead = (Integer) Class
685                         .forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
686                         .getMethod("RAND_load_file", String.class, long.class)
687                         .invoke(null, "/dev/urandom", 1024);
688                 if (bytesRead != 1024) {
689                     throw new IOException("Unexpected number of bytes read from Linux PRNG: "
690                             + bytesRead);
691                 }
692             } catch (Exception e) {
693                 if (ALLOW_BROKEN_PRNG) {
694                     Logger.getLogger(PrngFixes.class.getSimpleName()).warning("Failed to seed OpenSSL PRNG" + e.getMessage());
695                 } else {
696                     throw new SecurityException("Failed to seed OpenSSL PRNG", e);
697                 }
698             }*/
699         }
700 
701         /**
702          * Installs a Linux PRNG-backed {@code SecureRandom} implementation as
703          * the default. Does nothing if the implementation is already the
704          * default or if there is not need to install the implementation.
705          *
706          * @throws SecurityException if the fix is needed but could not be
707          *             applied.
708          */
709         private static void installLinuxPRNGSecureRandom() throws SecurityException {
710 
711 
712 
713             // Install a Linux PRNG-based SecureRandom implementation as the
714             // default, if not yet installed.
715             Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
716 
717             // Insert and check the provider atomically.
718             // The official Android Java libraries use synchronized methods for
719             // insertProviderAt, etc., so synchronizing on the class should
720             // make things more stable, and prevent race conditions with other
721             // versions of this code.
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                 // Assert that new SecureRandom() and
730                 // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
731                 // by the Linux PRNG-based SecureRandom implementation.
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          * {@code Provider} of {@code SecureRandom} engines which pass through
772          * all requests to the Linux PRNG.
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                 // Although /dev/urandom is not a SHA-1 PRNG, some apps
780                 // explicitly request a SHA1PRNG SecureRandom and we thus need
781                 // to prevent them from getting the default implementation whose
782                 // output may have low entropy.
783                 put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
784                 put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
785             }
786         }
787 
788         /**
789          * {@link SecureRandomSpi} which passes all requests to the Linux PRNG (
790          * {@code /dev/urandom}).
791          */
792         public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
793 
794             /*
795              * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
796              * seed are passed through to the Linux PRNG (/dev/urandom).
797              * Instances of this class seed themselves by mixing in the current
798              * time, PID, UID, build fingerprint, and hardware serial number
799              * (where available) into Linux PRNG.
800              *
801              * Concurrency: Read requests to the underlying Linux PRNG are
802              * serialized (on sLock) to ensure that multiple threads do not get
803              * duplicated PRNG output.
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              * Input stream for reading from Linux PRNG or {@code null} if not
812              * yet opened.
813              *
814              * @GuardedBy("sLock")
815              */
816             private static DataInputStream sUrandomIn;
817 
818             /**
819              * Output stream for writing to Linux PRNG or {@code null} if not
820              * yet opened.
821              *
822              * @GuardedBy("sLock")
823              */
824             private static OutputStream sUrandomOut;
825 
826             /**
827              * Whether this engine instance has been seeded. This is needed
828              * because each instance needs to seed itself if the client does not
829              * explicitly seed it.
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                     // On a small fraction of devices /dev/urandom is not
845                     // writable Log and ignore.
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                     // Mix in the device- and invocation-specific seed.
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                         // NOTE: Consider inserting a BufferedInputStream
887                         // between DataInputStream and FileInputStream if you need
888                         // higher PRNG output performance and can live with future PRNG
889                         // output being pulled into this process prematurely.
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          * Generates a device- and invocation-specific seed to be mixed into the
913          * Linux PRNG.
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 }