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