Use of Android KeyStore

During our App development process, some sensitive and secure data may need to be encrypted, such as the storage of login tokens. We often use some encryption algorithms to encrypt these sensitive data and then save them, and then decrypt them when they need to be taken out.

At this point, there will be a question: how should the Key used for encryption and decryption be stored?

  • If the Key and the encrypted data are stored together, there is a certain security risk.
  • Encrypt the Key again, and it will fall into an endless loop.

In order to ensure security, Android provides KeyStorea system to save the Key. This article will briefly explore KeyStoreand use it.

1. What is KeyStore? How to ensure security?

1. What is KeyStore?

Let's take a look at the official definition of him:

This class represents a storage facility for cryptographic keys and certificates.

This class represents a storage facility for encryption keys and certificates

The definition is actually very simple, it is used to save the Key and certificate for encryption and decryption.

2. How to ensure safety?

The security protection measures are written in the official document ( Android Keystore System | Android Developers | Android Developers (google.cn) ), I will summarize it according to my understanding:

  • The key is stored in the mobile phone system, and when the application process obtains the key, it is obtained into the memory through the system process. That is to say, the Key can only be obtained in this application process, and it is impossible to extract the Key.
  • KeyStoreYou can also specify the authorized use method of the key (such as user security lock verification), which can ensure that the key can only be obtained under the authorization.

Generally speaking, the user can only use the Key stored in the application when the application is running KeyStore. Unless the attacker gets the source code to break the point and debug, otherwise he cannot know what the Key is. This ensures the security of the stored Key.

Second, the use of KeyStore

KeyStoreThere are many supported encryption algorithms, some of which have API Level requirements, for details, please refer to Android Keystore System | Android Developers | Android Developers (google.cn)

This article takes the AES encryption algorithm as an example to briefly explain KeyStorehow to use it. Note that the code involved in this article requires minSdk>=23.

First simply implement the encryption and decryption of the AES algorithm

1. Data encryption

object AESUtil {
    
    

    private const val IV_BLOCK_SIZE = 16

    fun encryptAES(encryptBytes: ByteArray, encryptKey: SecretKey): ByteArray?{
    
    
        try {
    
    
            //创建密码器
            val cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING")
            //用密钥初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, encryptKey)
            val final = cipher.doFinal(encryptBytes)
            // iv占前16位,加密后的数据占后面
            return cipher.iv + final
        } catch (e: NoSuchPaddingException) {
    
    
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
    
    
            e.printStackTrace()
        } catch (e: InvalidAlgorithmParameterException) {
    
    
            e.printStackTrace()
        } catch (e: InvalidKeyException) {
    
    
            e.printStackTrace()
        } catch (e: BadPaddingException) {
    
    
            e.printStackTrace()
        } catch (e: IllegalBlockSizeException) {
    
    
            e.printStackTrace()
        }
        return null
    }

    fun decryptAES(decryptBytes: ByteArray, decryptKey: SecretKey): ByteArray? {
    
    
        try {
    
    
            // 先取出IV
            val iv = decryptBytes.copyOfRange(0, IV_BLOCK_SIZE)
            // 取出加密后的数据
            val decryptData = decryptBytes.copyOfRange(IV_BLOCK_SIZE, decryptBytes.size)
            val cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING")
            cipher.init(Cipher.DECRYPT_MODE, decryptKey, IvParameterSpec(iv))
            return cipher.doFinal(decryptData)
        } catch (e: NoSuchPaddingException) {
    
    
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
    
    
            e.printStackTrace()
        } catch (e: InvalidAlgorithmParameterException) {
    
    
            e.printStackTrace()
        } catch (e: InvalidKeyException) {
    
    
            e.printStackTrace()
        } catch (e: BadPaddingException) {
    
    
            e.printStackTrace()
        } catch (e: IllegalBlockSizeException) {
    
    
            e.printStackTrace()
        }
        return null
    }

}

Then we need to generate a Key for encryption, through KeyGenerator, first generate a KeyGenerator

	private fun getKeyGenerator(alias: String): KeyGenerator {
    
    
        // 第一个参数指定加密算法,第二个参数指定Provider
        val keyGenerator = KeyGenerator.getInstance("AES", "AndroidKeyStore")
        val parameterSpec = KeyGenParameterSpec.Builder(
            alias,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT  //用于加密和解密
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)  // AEC_CBC
            .setUserAuthenticationRequired(false)   // 是否需要用户认证
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)  //AES算法的PADDING, 和前面的AESUtil里保持统一
            .build()
        keyGenerator.init(parameterSpec)
        return keyGenerator
    }

This method accepts an alias parameter, which is an alias for generating Key, which will be KeyStoreused later when fetching from it.

After generation KeyGenerator, the Key required for encryption and decryption can be generated:

	val key: SecretKey = getKeyGenerator("myKey").generateKey()

Then we can encrypt and store the data that needs to be protected.

	val srcData = "hello world"
	val encryptData = AESUtil.encryptAES(srcData.toByteArray(), key)
	// 存储加密后的数据
	...

2. Obtain Key decryption from KeyStore

We have encrypted and stored the data before, and the next step is to take out the data and decrypt it for use.

We take out our decrypted Key KeyStorefrom it

    fun getKeyFromKeyStore(alias: String): SecretKey? {
    
    
        // 参数为Provider
        val keyStore = KeyStore.getInstance("AndroidKeyStore")
        // 一定要先初始化
        keyStore.load(null)
        // 获取KeyStore中的所有Key的别名
        val aliases = keyStore.aliases()
        // KeyStore里没有key
        if (!aliases.hasMoreElements()) {
    
    
            return null
        }
        // Key的保护参数,这里为不需要密码
        val protParam: KeyStore.ProtectionParameter =
            KeyStore.PasswordProtection(null)
        // 通过别名获取Key
        val entry = keyStore.getEntry(alias, protParam) as KeyStore.SecretKeyEntry
        return entry.secretKey
    }

The comments of each step are written in the code. aliasThe parameters of the method here are KeyGeneratorthe parameters we generated when we generated the Key before.

Then you can get the Key to decrypt.

	val decryptKey = KeyStoreUtil.getKeyFromKeyStore(KEY_ALIAS)
	decryptKey?.let {
    
    
        // 解密数据
        val decryptAES = AESUtil.decryptAES(encryptData, decryptKey)
    }

At this point, KeyStorethe simple use is over.

3. Source code analysis

Careful readers may find the problem. In the previous use, ** did not store the Key in it KeyStore. Why can it be taken out later? **If you want to figure out this problem, you have to solve it through the source code.

First formulate an idea for analyzing the problem:

  1. KeyStoreWhere did you get it from?
  2. KeyGeneratorHow is it stored when the Key is generated?

1. Where is the KeyStore obtained from?

KeyStoreThe main way to get the Key is getEntrythat the source code of this method is very clear and simple

	public final Entry getEntry(String alias, ProtectionParameter protParam)
                throws NoSuchAlgorithmException, UnrecoverableEntryException,
                KeyStoreException {
    
    

        if (alias == null) {
    
    
            throw new NullPointerException("invalid null input");
        }
        if (!initialized) {
    
    
            throw new KeyStoreException("Uninitialized keystore");
        }
        return keyStoreSpi.engineGetEntry(alias, protParam);
    }

aliasFirst of all , it cannot be empty when you take it . This is an alias for taking the Key. If it is empty, you will naturally not know which Key you want.

Secondly, it will judge KeyStorewhether to initialize.

The core code is the last line.

Here KeyStoreSpiis a abstractclass, which implements engineGetEntrythe method.

public KeyStore.Entry engineGetEntry(String alias,
                    KeyStore.ProtectionParameter protParam)
            throws KeyStoreException, NoSuchAlgorithmException,
            UnrecoverableEntryException {
    
    

	...
    if ((protParam == null) || protParam instanceof KeyStore.PasswordProtection) {
    
    
        if (engineIsCertificateEntry(alias)) {
    
    
            throw new UnsupportedOperationException
                ("trusted certificate entries are not password-protected");
        } else if (engineIsKeyEntry(alias)) {
    
    
            char[] password = null;
            if (protParam != null) {
    
    
                KeyStore.PasswordProtection pp =
                    (KeyStore.PasswordProtection)protParam;
                password = pp.getPassword();
            }
            Key key = engineGetKey(alias, password);
            ....
        }
    }

    ....
}

If you follow the source code, you will find that the actual implementation of Key is engineGetKey()obtained through a method, and this method is an abstract method, that is, to find a specific implementation class.

The Provider we use is AndroidKeyStore, and the corresponding implementation class isAndroidKeyStoreSpi . Then go inside and take a look at engineGetKey()the implementation

	public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,UnrecoverableKeyException {
    
    
        try {
    
    
            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore,alias,mNamespace);
        }
        ....
    }

The core code inside is only one sentence, continue to dig deep into AndroidKeyStoreProviderit.

	public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
            @NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)
            throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
    
    
        ....
        final AndroidKeyStoreKey key = loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor);
        if (key instanceof AndroidKeyStorePublicKey) {
    
    
            return ((AndroidKeyStorePublicKey) key).getPrivateKey();
        } else {
    
    
            return key;
        }
    }

The core code is loadAndroidKeyStoreKeyFromKeystore()the method

	private static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
            @NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor)
            throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
    
    
        KeyEntryResponse response = null;
        try {
    
    
            // 核心代码
            response = keyStore.getKeyEntry(descriptor);
        } catch (android.security.KeyStoreException e) {
    
    
           	....
            }
        }
		...
    }

Finally, you can see the place where you finally get the Key, but here keyStoreyou should pay attention to the following, which is this class under Android KeyStore2.

	/**
     * Retrieves a key entry from the keystore backend.
     * @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details.
     * @param descriptor
     * @return
     * @throws KeyStoreException
     * @hide
     */
    public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)
            throws KeyStoreException {
    
    
        return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
    }

As you can see from the comments, KeyStorethe way to get the Key is through IKeystoreServicethis service, that is, through the system process. **Here we mainly check where to get it, and readers can take a look for more details on how to get it IKeystoreService.

2. How to save it?

Earlier we figured out where it was taken from, and then we have to take a look at how it was stored.

KeyStoreThe way to save the Key is setEntry(), let's start from here.

	public final void setEntry(String alias, Entry entry,
                        ProtectionParameter protParam)
                throws KeyStoreException {
    
    
        if (alias == null || entry == null) {
    
    
            throw new NullPointerException("invalid null input");
        }
        if (!initialized) {
    
    
            throw new KeyStoreException("Uninitialized keystore");
        }
        keyStoreSpi.engineSetEntry(alias, entry, protParam);
    }

KeyStoreAs you can see, it was still handed over when it was saved KeyStoreSpi. And KeyStoreSpithe core method is engineSetKeyEntry()that we go directly AndroidKeyStoreSpito see the specific implementation.

	@Override
    public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
            throws KeyStoreException {
    
    
        if ((password != null) && (password.length > 0)) {
    
    
            throw new KeyStoreException("entries cannot be protected with passwords");
        }

        if (key instanceof PrivateKey) {
    
    
            setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
        } else if (key instanceof SecretKey) {
    
    
            setSecretKeyEntry(alias, (SecretKey) key, null);
        } else {
    
    
            throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
        }
    }

Here is a brief introduction:

  • PrivateKeyIt is usually the private key of an asymmetric encryption algorithm, the public key is used for encryption, and the private key is used for decryption, such as the RSA algorithm.

  • SecretKeyIt is usually the key of a symmetric encryption algorithm, which is used for encryption and decryption, such as the AES algorithm.

Then look at setSecretKeyEntry()the method

private void setSecretKeyEntry(String alias, SecretKey key,
            java.security.KeyStore.ProtectionParameter param)
            throws KeyStoreException {
    
    
        ...

        try {
    
    
            KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel(
                    securityLevel);

            KeyDescriptor descriptor = makeKeyDescriptor(alias);

            securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */,
                    importArgs, flags, keyMaterial);
        } catch (android.security.KeyStoreException e) {
    
    
            throw new KeyStoreException("Failed to import secret key.", e);
        }
    }

The method is very long, but the final deposit method is here at the end.

It can be seen that in the end, KeyStoreSecurityLevelthis class importKey()is saved through the method.

	/**
     * Imports a key into Keystore.
     * @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
     */
    public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
            Collection<KeyParameter> args, int flags, byte[] keyData)
            throws KeyStoreException {
    
    
        return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
                args.toArray(new KeyParameter[args.size()]), flags, keyData));
    }

You can understand it from the comments, import Key into KeyStore

3. Has KeyGenerator saved it for us?

After figuring out how to save it, you can go to the source code of KeyGenerator to see if he really saved it for us directly.

	public final SecretKey generateKey() {
    
    
        if (serviceIterator == null) {
    
    
            return spi.engineGenerateKey();
        }
        ....
   }

KeyGeneratorSpiIt is also a abstactclass, and our specific implementation class here isAndroidKeyStoreKeyGeneratorSpi

@Override
    protected SecretKey engineGenerateKey() {
    
    
        ....
        KeyStoreSecurityLevel iSecurityLevel = null;
        try {
    
    
            iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
            metadata = iSecurityLevel.generateKey(
                    descriptor,
                    null, /* Attestation key not applicable to symmetric keys. */
                    params,
                    flags,
                    additionalEntropy);
        } catch (android.security.KeyStoreException e) {
    
    
            switch (e.getErrorCode()) {
    
    
                // TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec
                //      becomes available.
                case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
                    throw new StrongBoxUnavailableException("Failed to generate key");
                default:
                    throw new ProviderException("Keystore key generation failed", e);
            }
        }
       ....
        SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA,
                iSecurityLevel);
        return result;
    }

This method is also very long, but at the end you can see an old friend: KeyStoreSecurityLevel. It turns out that the final method to generate Key is to call his generateKey()method.

	/**
     * Generates a new key in Keystore.
     * @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
     */
    public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
            Collection<KeyParameter> args, int flags, byte[] entropy)
            throws KeyStoreException {
    
    
        return handleExceptions(() -> mSecurityLevel.generateKey(
                descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
                flags, entropy));
    }

Generate a new Key in KeyStore, which is obvious here.

KeyGeneratorIn the end, when the Key is generated, it will be directly generated in KeyStoreit, so we can get it directly.

Four. Summary

This article briefly introduces what is KeyStore, if you use KeyGeneratorand KeyStore, and KeyStoreanalyzes the source code of the access method.

Guess you like

Origin blog.csdn.net/qq_43478882/article/details/127392947