CryptoHelper.java revision 766b28329326628eaf1ef8009ebd5d611369c490
1package com.android.server.accounts;
2
3import android.annotation.NonNull;
4import android.annotation.Nullable;
5import android.os.Bundle;
6import android.os.Parcel;
7import android.util.Log;
8
9import com.android.internal.util.Preconditions;
10
11import java.security.GeneralSecurityException;
12import java.security.NoSuchAlgorithmException;
13
14import javax.crypto.Cipher;
15import javax.crypto.KeyGenerator;
16import javax.crypto.Mac;
17import javax.crypto.SecretKey;
18import javax.crypto.spec.IvParameterSpec;
19
20/**
21 * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
22 * key for {@link AccountManagerService}.
23 */
24/* default */ class CryptoHelper {
25    private static final String TAG = "Account";
26
27    private static final String KEY_CIPHER = "cipher";
28    private static final String KEY_MAC = "mac";
29    private static final String KEY_ALGORITHM = "AES";
30    private static final String KEY_IV = "iv";
31    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
32    private static final String MAC_ALGORITHM = "HMACSHA256";
33    private static final int IV_LENGTH = 16;
34
35    private static CryptoHelper sInstance;
36    // Keys used for encrypting and decrypting data returned in a Bundle.
37    private final SecretKey mEncryptionKey;
38    private final SecretKey mMacKey;
39
40    /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
41        if (sInstance == null) {
42            sInstance = new CryptoHelper();
43        }
44        return sInstance;
45    }
46
47    private CryptoHelper() throws NoSuchAlgorithmException {
48        KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
49        mEncryptionKey = kgen.generateKey();
50        // Use a different key for mac-ing than encryption/decryption.
51        kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
52        mMacKey = kgen.generateKey();
53    }
54
55    @NonNull
56    /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
57        Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
58        Parcel parcel = Parcel.obtain();
59        bundle.writeToParcel(parcel, 0);
60        byte[] clearBytes = parcel.marshall();
61        parcel.recycle();
62
63        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
64        cipher.init(Cipher.ENCRYPT_MODE, mEncryptionKey);
65        byte[] encryptedBytes = cipher.doFinal(clearBytes);
66        byte[] iv = cipher.getIV();
67        byte[] mac = createMac(encryptedBytes, iv);
68
69        Bundle encryptedBundle = new Bundle();
70        encryptedBundle.putByteArray(KEY_CIPHER, encryptedBytes);
71        encryptedBundle.putByteArray(KEY_MAC, mac);
72        encryptedBundle.putByteArray(KEY_IV, iv);
73
74        return encryptedBundle;
75    }
76
77    @Nullable
78    /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
79        Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
80        byte[] iv = bundle.getByteArray(KEY_IV);
81        byte[] encryptedBytes = bundle.getByteArray(KEY_CIPHER);
82        byte[] mac = bundle.getByteArray(KEY_MAC);
83        if (!verifyMac(encryptedBytes, iv, mac)) {
84            Log.w(TAG, "Escrow mac mismatched!");
85            return null;
86        }
87
88        IvParameterSpec ivSpec = new IvParameterSpec(iv);
89        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
90        cipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, ivSpec);
91        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
92
93        Parcel decryptedParcel = Parcel.obtain();
94        decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
95        decryptedParcel.setDataPosition(0);
96        Bundle decryptedBundle = new Bundle();
97        decryptedBundle.readFromParcel(decryptedParcel);
98        decryptedParcel.recycle();
99        return decryptedBundle;
100    }
101
102    private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray)
103            throws GeneralSecurityException {
104        if (cipherArray == null || cipherArray.length == 0 || macArray == null
105                || macArray.length == 0) {
106            if (Log.isLoggable(TAG, Log.VERBOSE)) {
107                Log.v(TAG, "Cipher or MAC is empty!");
108            }
109            return false;
110        }
111        return constantTimeArrayEquals(macArray, createMac(cipherArray, iv));
112    }
113
114    @NonNull
115    private byte[] createMac(@NonNull byte[] cipher, @NonNull byte[] iv) throws GeneralSecurityException {
116        Mac mac = Mac.getInstance(MAC_ALGORITHM);
117        mac.init(mMacKey);
118        mac.update(cipher);
119        mac.update(iv);
120        return mac.doFinal();
121    }
122
123    private static boolean constantTimeArrayEquals(byte[] a, byte[] b) {
124        if (a == null || b == null) {
125            return a == b;
126        }
127        if (a.length != b.length) {
128            return false;
129        }
130        boolean isEqual = true;
131        for (int i = 0; i < b.length; i++) {
132            isEqual &= (a[i] == b[i]);
133        }
134        return isEqual;
135    }
136}
137