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