2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.locksettings;
19 import android.security.keystore.KeyProperties;
20 import android.security.keystore.KeyProtection;
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;
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;
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;
53 private static byte[] decrypt(SecretKey key, byte[] blob)
54 throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
55 InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
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);
67 private static byte[] encrypt(SecretKey key, byte[] blob)
68 throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
69 InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
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);
82 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
83 outputStream.write(iv);
84 outputStream.write(ciphertext);
85 return outputStream.toByteArray();
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);
93 return encrypt(key, message);
94 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
95 | IllegalBlockSizeException | BadPaddingException | IOException e) {
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);
106 return decrypt(key, ciphertext);
107 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
108 | IllegalBlockSizeException | BadPaddingException
109 | InvalidAlgorithmParameterException e) {
115 public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
117 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
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) {
125 throw new RuntimeException("Failed to decrypt blob", e);
129 public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
131 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
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) {
143 throw new RuntimeException("Failed to decrypt blob", e);
147 public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
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");
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);
159 builder.setUserAuthenticationRequired(true)
160 .setBoundToSpecificSecureUserId(sid)
161 .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
164 keyStore.setEntry(keyAlias,
165 new KeyStore.SecretKeyEntry(secretKey),
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) {
174 throw new RuntimeException("Failed to encrypt blob", e);
178 public static void destroyBlobKey(String keyAlias) {
181 keyStore = KeyStore.getInstance("AndroidKeyStore");
183 keyStore.deleteEntry(keyAlias);
184 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
190 protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
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");
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) {
204 return digest.digest();
205 } catch (NoSuchAlgorithmException e) {
206 throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);