1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser.crypto;
7 import android.os.AsyncTask;
8 import android.os.Bundle;
9 import android.util.Log;
11 import java.security.GeneralSecurityException;
12 import java.security.Key;
13 import java.security.SecureRandom;
14 import java.util.Arrays;
15 import java.util.concurrent.Callable;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.FutureTask;
19 import javax.annotation.concurrent.ThreadSafe;
20 import javax.crypto.Cipher;
21 import javax.crypto.KeyGenerator;
22 import javax.crypto.spec.IvParameterSpec;
23 import javax.crypto.spec.SecretKeySpec;
26 * Generates {@link Cipher} instances for encrypting session data that is temporarily stored.
28 * When an Activity is sent to the background, Android gives it the opportunity to save state to
29 * restore a user's session when the Activity is restarted. In addition to saving state to disk,
30 * Android has a mechanism for saving instance state through {@link Bundle}s, which help
31 * differentiate between users pausing and ending a session:
32 * - If the Activity is killed in the background (e.g. to free up resources for other Activities),
33 * Android gives a {@link Bundle} to the Activity when the user restarts the Activity. The
34 * {@link Bundle} is expected to be small and fast to generate, and is managed by Android.
35 * - If the Activity was explicitly killed (e.g. the user swiped away the task from Recent Tasks),
36 * Android does not restore the {@link Bundle} when the user restarts the Activity.
38 * To securely save temporary session data to disk:
39 * - Encrypt data with a {@link Cipher} from {@link CipherFactory#getCipher(int)} before storing it.
40 * - Store {@link Cipher} parameters in the Bundle via {@link CipherFactory#saveToBundle(Bundle)}.
42 * Explicitly ending the session destroys the {@link Bundle}, making the previous session's data
46 public class CipherFactory {
47 private static final String TAG = "CipherFactory";
48 static final int NUM_BYTES = 16;
50 static final String BUNDLE_IV = "org.chromium.content.browser.crypto.CipherFactory.IV";
51 static final String BUNDLE_KEY = "org.chromium.content.browser.crypto.CipherFactory.KEY";
53 /** Holds intermediate data for the computation. */
54 private static class CipherData {
56 public final byte[] iv;
58 public CipherData(Key key, byte[] iv) {
64 /** Singleton holder for the class. */
65 private static class LazyHolder {
66 private static CipherFactory sInstance = new CipherFactory();
70 * Synchronization primitive to prevent thrashing the cipher parameters between threads
71 * attempting to restore previous parameters and generate new ones.
73 private final Object mDataLock = new Object();
75 /** Used to generate data needed for the Cipher on a background thread. */
76 private FutureTask<CipherData> mDataGenerator;
78 /** Holds data for cipher generation. */
79 private CipherData mData;
81 /** Generates random data for the Ciphers. May be swapped out for tests. */
82 private ByteArrayGenerator mRandomNumberProvider;
84 /** @return The Singleton instance. Creates it if it doesn't exist. */
85 public static CipherFactory getInstance() {
86 return LazyHolder.sInstance;
90 * Creates a secure Cipher for encrypting data.
91 * This function blocks until data needed to generate a Cipher has been created by the
93 * @param opmode One of Cipher.{ENCRYPT,DECRYPT}_MODE.
94 * @return A Cipher, or null if it is not possible to instantiate one.
96 public Cipher getCipher(int opmode) {
97 CipherData data = getCipherData(true);
101 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
102 cipher.init(opmode, data.key, new IvParameterSpec(data.iv));
104 } catch (GeneralSecurityException e) {
105 // Can't do anything here.
109 Log.e(TAG, "Error in creating cipher instance.");
114 * Returns data required for generating the Cipher.
115 * @param generateIfNeeded Generates data on the background thread, blocking until it is done.
116 * @return Data to use for the Cipher, null if it couldn't be generated.
118 CipherData getCipherData(boolean generateIfNeeded) {
119 if (mData == null && generateIfNeeded) {
120 // Ideally, this task should have been started way before this.
121 triggerKeyGeneration();
123 // Grab the data from the task.
126 data = mDataGenerator.get();
127 } catch (InterruptedException e) {
128 throw new RuntimeException(e);
129 } catch (ExecutionException e) {
130 throw new RuntimeException(e);
133 // Only the first thread is allowed to save the data.
134 synchronized (mDataLock) {
135 if (mData == null) mData = data;
142 * Creates a Callable that generates the data required to create a Cipher. This is done on a
143 * background thread to prevent blocking on the I/O required for
144 * {@link ByteArrayGenerator#getBytes(int)}.
145 * @return Callable that generates the Cipher data.
147 private Callable<CipherData> createGeneratorCallable() {
148 return new Callable<CipherData>() {
150 public CipherData call() {
151 // Poll random data to generate initialization parameters for the Cipher.
154 seed = mRandomNumberProvider.getBytes(NUM_BYTES);
155 iv = mRandomNumberProvider.getBytes(NUM_BYTES);
156 } catch (Exception e) {
157 Log.e(TAG, "Couldn't get generator data.");
162 // Old versions of SecureRandom do not seed themselves as securely as possible.
163 // This workaround should suffice until the fixed version is deployed to all
164 // users. The seed comes from RandomNumberProvider.getBytes(), which reads
165 // from /dev/urandom, which is as good as the platform can get.
167 // TODO(palmer): Consider getting rid of this once the updated platform has
168 // shipped to everyone. Alternately, leave this in as a defense against other
169 // bugs in SecureRandom.
170 SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
171 random.setSeed(seed);
173 KeyGenerator generator = KeyGenerator.getInstance("AES");
174 generator.init(128, random);
175 return new CipherData(generator.generateKey(), iv);
176 } catch (GeneralSecurityException e) {
177 Log.e(TAG, "Couldn't get generator instances.");
185 * Generates the encryption key and IV on a background thread (if necessary).
186 * Should be explicitly called when the Activity determines that it will need a Cipher rather
187 * than immediately calling {@link CipherFactory#getCipher(int)}.
189 public void triggerKeyGeneration() {
190 if (mData != null) return;
192 synchronized (mDataLock) {
193 if (mDataGenerator == null) {
194 mDataGenerator = new FutureTask<CipherData>(createGeneratorCallable());
195 AsyncTask.THREAD_POOL_EXECUTOR.execute(mDataGenerator);
201 * Saves the encryption data in a bundle. Expected to be called when an Activity saves its state
202 * before being sent to the background.
204 * The IV *could* go into the first block of the payload. However, since the staleness of the
205 * data is determined by whether or not it's able to be decrypted, the IV should not be read
208 * @param outState The data bundle to store data into.
210 public void saveToBundle(Bundle outState) {
211 CipherData data = getCipherData(false);
212 if (data == null) return;
214 byte[] wrappedKey = data.key.getEncoded();
215 if (wrappedKey != null && data.iv != null) {
216 outState.putByteArray(BUNDLE_KEY, wrappedKey);
217 outState.putByteArray(BUNDLE_IV, data.iv);
222 * Restores the encryption key from the given Bundle. Expected to be called when an Activity is
223 * being restored after being killed in the background. If the Activity was explicitly killed by
224 * the user, Android gives no Bundle (and therefore no key).
226 * @param savedInstanceState Bundle containing the Activity's previous state. Null if the user
227 * explicitly killed the Activity.
228 * @return True if the data was restored successfully from the Bundle, or if
229 * the CipherData in use matches the Bundle contents.
232 public boolean restoreFromBundle(Bundle savedInstanceState) {
233 if (savedInstanceState == null) return false;
235 byte[] wrappedKey = savedInstanceState.getByteArray(BUNDLE_KEY);
236 byte[] iv = savedInstanceState.getByteArray(BUNDLE_IV);
237 if (wrappedKey == null || iv == null) return false;
240 Key bundledKey = new SecretKeySpec(wrappedKey, "AES");
241 synchronized (mDataLock) {
243 mData = new CipherData(bundledKey, iv);
245 } else if (mData.key.equals(bundledKey) && Arrays.equals(mData.iv, iv)) {
248 Log.e(TAG, "Attempted to restore different cipher data.");
251 } catch (IllegalArgumentException e) {
252 Log.e(TAG, "Error in restoring the key from the bundle.");
259 * Overrides the random number generated that is normally used by the class.
260 * @param mockProvider Should be used to provide non-random data.
262 void setRandomNumberProviderForTests(ByteArrayGenerator mockProvider) {
263 mRandomNumberProvider = mockProvider;
266 private CipherFactory() {
267 mRandomNumberProvider = new ByteArrayGenerator();