1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.dialer.storage;
18 
19 import android.security.keystore.KeyGenParameterSpec;
20 import android.security.keystore.KeyProperties;
21 import android.util.Log;
22 
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.annotation.WorkerThread;
26 import androidx.room.TypeConverter;
27 
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 import java.security.InvalidAlgorithmParameterException;
32 import java.security.InvalidKeyException;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.NoSuchProviderException;
37 import java.security.UnrecoverableKeyException;
38 import java.security.cert.CertificateException;
39 
40 import javax.crypto.BadPaddingException;
41 import javax.crypto.Cipher;
42 import javax.crypto.IllegalBlockSizeException;
43 import javax.crypto.KeyGenerator;
44 import javax.crypto.NoSuchPaddingException;
45 import javax.crypto.SecretKey;
46 import javax.crypto.spec.GCMParameterSpec;
47 
48 /**
49  * A converter that does the encryption and decryption using android KeyStore system. See
50  * https://developer.android.com/training/articles/keystore
51  */
52 public class CipherConverter {
53     private static final String TAG = "CD.CipherConverter";
54     private static final String KEY_STORE_ALIAS = "cd-cipher-converter";
55     private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
56 
57     /**
58      * Decryption.
59      *
60      * @param encryptedData the encrypted byte array. First byte is the initialization vector
61      *                      length, followed by the
62      *                      initialization vector and then the encrypted string.
63      * @return the decrypted string wrapper. It might be null if the encrypted array is not valid or
64      * exception happens during decryption.
65      */
66     @WorkerThread
67     @TypeConverter
68     @Nullable
decrypt(@onNull byte[] encryptedData)69     public CipherWrapper<String> decrypt(@NonNull byte[] encryptedData) {
70         if (encryptedData.length == 0) {
71             return null;
72         }
73 
74         try {
75             KeyStore ks = getKeyStore();
76             SecretKey decryptionKey = (SecretKey) ks.getKey(KEY_STORE_ALIAS, null);
77 
78             Cipher cipher = getCipherInstance();
79             ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(encryptedData);
80 
81             int ivLength = byteArrayInputStream.read();
82 
83             byte[] iv = new byte[ivLength];
84             byteArrayInputStream.read(iv, 0, ivLength);
85 
86             byte[] encryptedPhoneNumber = new byte[encryptedData.length - ivLength - 1];
87             byteArrayInputStream.read(encryptedPhoneNumber);
88 
89             cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
90             byte[] decryptionResult = cipher.doFinal(encryptedPhoneNumber);
91             String decryptString = new String(decryptionResult, "UTF-8");
92             return new CipherWrapper(decryptString);
93         } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException
94                 | UnrecoverableKeyException | NoSuchPaddingException | BadPaddingException
95                 | IllegalBlockSizeException | InvalidKeyException
96                 | InvalidAlgorithmParameterException e) {
97             Log.e(TAG, e.toString());
98         }
99         return null;
100     }
101 
102     /**
103      * Encryption.
104      *
105      * @param stringCipherWrapper The wrapper of string to be encrypted.
106      * @return byte array that includes the iv length, iv and encrypted string.
107      */
108     @WorkerThread
109     @NonNull
110     @TypeConverter
encrypt(CipherWrapper<String> stringCipherWrapper)111     public byte[] encrypt(CipherWrapper<String> stringCipherWrapper) {
112         try {
113             KeyStore ks = getKeyStore();
114             SecretKey secretKey;
115             if (ks.containsAlias(KEY_STORE_ALIAS)) {
116                 secretKey = (SecretKey) ks.getKey(KEY_STORE_ALIAS, null);
117             } else {
118                 KeyGenerator kpg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
119                         ANDROID_KEY_STORE);
120                 KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
121                         KEY_STORE_ALIAS,
122                         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
123                         .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
124                         .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
125                         .build();
126                 kpg.init(keyGenParameterSpec);
127                 secretKey = kpg.generateKey();
128             }
129 
130             Cipher cipher = getCipherInstance();
131             cipher.init(Cipher.ENCRYPT_MODE, secretKey);
132             byte[] iv = cipher.getIV();
133             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
134             outputStream.write(iv.length);
135             outputStream.write(iv);
136             byte[] encryptionResult = cipher.doFinal(
137                     stringCipherWrapper.get().getBytes("UTF-8"));
138             outputStream.write(encryptionResult);
139             return outputStream.toByteArray();
140         } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException
141                 | UnrecoverableKeyException | NoSuchProviderException | NoSuchPaddingException
142                 | BadPaddingException | IllegalBlockSizeException | InvalidKeyException
143                 | InvalidAlgorithmParameterException e) {
144             Log.e(TAG, e.toString());
145         }
146         return new byte[0];
147     }
148 
getKeyStore()149     private KeyStore getKeyStore()
150             throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
151         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
152         keyStore.load(null);
153         return keyStore;
154     }
155 
getCipherInstance()156     private Cipher getCipherInstance()
157             throws NoSuchAlgorithmException, NoSuchPaddingException {
158         return Cipher.getInstance(
159                 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
160                         + KeyProperties.ENCRYPTION_PADDING_NONE);
161     }
162 }
163