1 /* 2 * Copyright (C) 2017 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.server.locksettings; 18 19 import android.security.keystore.KeyProperties; 20 import android.security.keystore.KeyProtection; 21 22 import java.io.ByteArrayOutputStream; 23 import java.io.IOException; 24 import java.security.InvalidAlgorithmParameterException; 25 import java.security.InvalidKeyException; 26 import java.security.KeyStore; 27 import java.security.KeyStoreException; 28 import java.security.MessageDigest; 29 import java.security.NoSuchAlgorithmException; 30 import java.security.SecureRandom; 31 import java.security.UnrecoverableKeyException; 32 import java.security.cert.CertificateException; 33 import java.security.spec.InvalidParameterSpecException; 34 import java.util.Arrays; 35 36 import javax.crypto.BadPaddingException; 37 import javax.crypto.Cipher; 38 import javax.crypto.IllegalBlockSizeException; 39 import javax.crypto.KeyGenerator; 40 import javax.crypto.NoSuchPaddingException; 41 import javax.crypto.SecretKey; 42 import javax.crypto.spec.GCMParameterSpec; 43 import javax.crypto.spec.SecretKeySpec; 44 45 public class SyntheticPasswordCrypto { 46 private static final int PROFILE_KEY_IV_SIZE = 12; 47 private static final int DEFAULT_TAG_LENGTH_BITS = 128; 48 private static final int AES_KEY_LENGTH = 32; // 256-bit AES key 49 private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes(); 50 // Time between the user credential is verified with GK and the decryption of synthetic password 51 // under the auth-bound key. This should always happen one after the other, but give it 15 52 // seconds just to be sure. 53 private static final int USER_AUTHENTICATION_VALIDITY = 15; 54 decrypt(SecretKey key, byte[] blob)55 private static byte[] decrypt(SecretKey key, byte[] blob) 56 throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, 57 InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { 58 if (blob == null) { 59 return null; 60 } 61 byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE); 62 byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length); 63 Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" 64 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); 65 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv)); 66 return cipher.doFinal(ciphertext); 67 } 68 encrypt(SecretKey key, byte[] blob)69 private static byte[] encrypt(SecretKey key, byte[] blob) 70 throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, 71 InvalidKeyException, IllegalBlockSizeException, BadPaddingException, 72 InvalidParameterSpecException { 73 if (blob == null) { 74 return null; 75 } 76 Cipher cipher = Cipher.getInstance( 77 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" 78 + KeyProperties.ENCRYPTION_PADDING_NONE); 79 cipher.init(Cipher.ENCRYPT_MODE, key); 80 byte[] ciphertext = cipher.doFinal(blob); 81 byte[] iv = cipher.getIV(); 82 if (iv.length != PROFILE_KEY_IV_SIZE) { 83 throw new RuntimeException("Invalid iv length: " + iv.length); 84 } 85 final GCMParameterSpec spec = cipher.getParameters().getParameterSpec( 86 GCMParameterSpec.class); 87 if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) { 88 throw new RuntimeException("Invalid tag length: " + spec.getTLen()); 89 } 90 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 91 outputStream.write(iv); 92 outputStream.write(ciphertext); 93 return outputStream.toByteArray(); 94 } 95 encrypt(byte[] keyBytes, byte[] personalisation, byte[] message)96 public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) { 97 byte[] keyHash = personalisedHash(personalisation, keyBytes); 98 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH), 99 KeyProperties.KEY_ALGORITHM_AES); 100 try { 101 return encrypt(key, message); 102 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException 103 | IllegalBlockSizeException | BadPaddingException | IOException 104 | InvalidParameterSpecException e) { 105 e.printStackTrace(); 106 return null; 107 } 108 } 109 decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext)110 public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) { 111 byte[] keyHash = personalisedHash(personalisation, keyBytes); 112 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH), 113 KeyProperties.KEY_ALGORITHM_AES); 114 try { 115 return decrypt(key, ciphertext); 116 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException 117 | IllegalBlockSizeException | BadPaddingException 118 | InvalidAlgorithmParameterException e) { 119 e.printStackTrace(); 120 return null; 121 } 122 } 123 decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId)124 public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) { 125 try { 126 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 127 keyStore.load(null); 128 129 SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null); 130 byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob); 131 return decrypt(decryptionKey, intermediate); 132 } catch (Exception e) { 133 e.printStackTrace(); 134 throw new RuntimeException("Failed to decrypt blob", e); 135 } 136 } 137 decryptBlob(String keyAlias, byte[] blob, byte[] applicationId)138 public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) { 139 try { 140 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 141 keyStore.load(null); 142 143 SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null); 144 byte[] intermediate = decrypt(decryptionKey, blob); 145 return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate); 146 } catch (CertificateException | IOException | BadPaddingException 147 | IllegalBlockSizeException 148 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException 149 | InvalidKeyException | UnrecoverableKeyException 150 | InvalidAlgorithmParameterException e) { 151 e.printStackTrace(); 152 throw new RuntimeException("Failed to decrypt blob", e); 153 } 154 } 155 createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid)156 public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) { 157 try { 158 KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); 159 keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom()); 160 SecretKey secretKey = keyGenerator.generateKey(); 161 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 162 keyStore.load(null); 163 KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) 164 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 165 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 166 .setCriticalToDeviceEncryption(true); 167 if (sid != 0) { 168 builder.setUserAuthenticationRequired(true) 169 .setBoundToSpecificSecureUserId(sid) 170 .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY); 171 } 172 173 keyStore.setEntry(keyAlias, 174 new KeyStore.SecretKeyEntry(secretKey), 175 builder.build()); 176 byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data); 177 return encrypt(secretKey, intermediate); 178 } catch (CertificateException | IOException | BadPaddingException 179 | IllegalBlockSizeException 180 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException 181 | InvalidKeyException 182 | InvalidParameterSpecException e) { 183 e.printStackTrace(); 184 throw new RuntimeException("Failed to encrypt blob", e); 185 } 186 } 187 destroyBlobKey(String keyAlias)188 public static void destroyBlobKey(String keyAlias) { 189 KeyStore keyStore; 190 try { 191 keyStore = KeyStore.getInstance("AndroidKeyStore"); 192 keyStore.load(null); 193 keyStore.deleteEntry(keyAlias); 194 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException 195 | IOException e) { 196 e.printStackTrace(); 197 } 198 } 199 personalisedHash(byte[] personalisation, byte[]... message)200 protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) { 201 try { 202 final int PADDING_LENGTH = 128; 203 MessageDigest digest = MessageDigest.getInstance("SHA-512"); 204 if (personalisation.length > PADDING_LENGTH) { 205 throw new RuntimeException("Personalisation too long"); 206 } 207 // Personalize the hash 208 // Pad it to the block size of the hash function 209 personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH); 210 digest.update(personalisation); 211 for (byte[] data : message) { 212 digest.update(data); 213 } 214 return digest.digest(); 215 } catch (NoSuchAlgorithmException e) { 216 throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e); 217 } 218 } 219 } 220