kotlin 中的 Android 密钥库 [英] Android Keystore in kotlin

查看:83
本文介绍了kotlin 中的 Android 密钥库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个需要非常安全的应用,因此我们希望使用 Android Keystore 来帮助使某些事情(例如后端的 accessTokens)更加安全.

我试过实现其中的一些,它似乎有效,但我主要是在示例之外工作,主要是 在 Android Keystore 中安全存储密钥

这是我的一些代码:

object AndroidKeyStore {const val ANDROID_KEY_STORE = "AndroidKeyStore"const val AES_MODE = "AES/GCM/NoPadding"私有变量 iv: ByteArray = byteArrayOf(55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44)//放在这里安全吗?const VAL SECRET_ALIAS = "测试"私人乐趣 generateSecretKey(keyAlias: String): SecretKey {val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)val spec = KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT 或 KeyProperties.PURPOSE_DECRYPT).setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).setRandomizedEncryptionRequired(false).建造()keyGenerator.init(spec)返回 keyGenerator.generateKey()}私人乐趣 getSecretKey(keyAlias: String): SecretKey {val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) }if (keyStore.getEntry(keyAlias, null) != null) {val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntryreturn secretKeyEntry.secretKey ?: generateSecretKey(keyAlias)}返回 generateSecretKey(keyAlias)}有趣的加密(数据:字符串):字符串{val cipher = Cipher.getInstance(AES_MODE)cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(SECRET_ALIAS), GCMParameterSpec(128, iv))iv = 密码.ivval 编码字节 = cipher.doFinal(data.toByteArray())返回 Base64.encodeToString(encodedBytes, Base64.NO_WRAP)}有趣的解密(加密:字符串):字符串{val cipher = Cipher.getInstance(AES_MODE)val spec = GCMParameterSpec(128, iv)cipher.init(Cipher.DECRYPT_MODE, getSecretKey(SECRET_ALIAS), 规范)val 编码字节 = Base64.decode(加密,Base64.NO_WRAP)val 解码 = cipher.doFinal(encodedBytes)返回字符串(已解码,Charsets.UTF_8)}

}

这是我的密钥库代码,它似乎可以加密/解密东西.但我很难准确地理解 Secret 别名是什么以及将它安全地放在哪里.

同时,我也在使用安全的偏好存储.所以我的想法是,将 accessToken 保存在安全首选项存储中,但使用此密钥库代码对其进行加密.

这是安全首选项存储的代码.

object SecurePreferenceUtils {枚举类 SecurePreferenceKeys {访问令牌,测试}有趣的 putString(key: SecurePreferenceKeys, value: String) {SecurePrefs.securePreferences.edit().putString(key.name, value).commit()}fun getString(key: SecurePreferenceKeys, defaultValue: String): String {返回 SecurePrefs.securePreferences.getString(key.name, defaultValue) ?: ""}对象 SecurePrefs {lateinit var securePreferences: SecurePreferences}

最后,当我从后端获取accessToken时,我希望能够做到这一点.

SecurePreferenceUtils.putString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt(""))val token = AndroidKeyStore.decrypt(SecurePreferenceUtils.getString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt("")))

希望我对我想去的地方有某种意义,我只是不太确定我在这里走的是正确的道路.

感谢任何帮助!

解决方案

别名是一键唯一的,如果要存储用户名和密码,需要2个唯一的别名

这里我是从资源 xml 文件中获取的它是任何唯一的字符串值

<string name="app_name">Data_Persistent</string><string name="app_package">com.aprorit.keystoreexample</string></资源>

<块引用>

如果 SDK 小于 18,这里我使用共享首选项,因为密钥库可用 18 个以上.并解码为base64并存储

如果 SDK 是 18 及以上,使用 Keystore

这是您访问它的方式

private const val PASSWORD_KEY = "password";private val passwordStorage: PasswordStorageHelper = PasswordStorageHelper(context)有趣的保存密码(密码:字符串){passwordStorage.setData(PASSWORD_KEY, password.toByteArray())}有趣的 getPassword() : String {返回字符串((passwordStorage.getData(PASSWORD_KEY)?:ByteArray(0)))}有趣的删除密码(){passwordStorage.remove(PASSWORD_KEY)}

这是 PasswordStorageHelper 类,几乎可以处理所有事情

import android.content.Context导入 android.content.SharedPreferences导入 android.os.Build导入 android.security.KeyChain导入 android.security.keystore.KeyGenParameterSpec导入 android.security.keystore.KeyInfo导入 android.security.keystore.KeyProperties导入 android.util.Base64导入 android.util.Log导入 androidx.annotation.RequiresApi导入 com.example.data_persistent.R导入 java.io.IOException导入 java.math.BigInteger导入 java.security.*导入 java.security.cert.CertificateException导入 java.security.spec.AlgorithmParameterSpec导入 java.security.spec.InvalidKeySpecException导入 java.util.*导入 javax.crypto.BadPaddingException导入 javax.crypto.Cipher导入 javax.crypto.IllegalBlockSizeException导入 javax.crypto.NoSuchPaddingException导入 javax.security.auth.x500.X500Principal类 PasswordStorageHelper(上下文:上下文){private val tag = "PasswordStorageHelper";private val PREFS_NAME = "SecureData";私有变量密码存储:密码存储接口?在里面 {passwordStorage = if (Build.VERSION.SDK_INT <18) {密码存储助手SDK16();} 别的 {密码存储助手SDK18();}var isInitialized: 布尔值?= 假;尝试 {isInitialized = passwordStorage?.init(context);} 捕获(例如:异常){Log.e(tag, "PasswordStorage 初始化错误:" + ex.message, ex);}if (isInitialized != true && passwordStorage 是 PasswordStorageHelperSDK18) {passwordStorage = PasswordStorageHelperSDK16();passwordStorage?.init(context);}}有趣的 setData(键:字符串?,数据:ByteArray?){passwordStorage?.setData(key!!, data ?: ByteArray(0))}fun getData(key: String?): ByteArray?{返回 passwordStorage?.getData(key ?: ")}有趣的删除(键:字符串?){passwordStorage?.remove(key ?: ")}私有接口 PasswordStorageInterface {有趣的初始化(上下文:上下文?):布尔值fun setData(key: String?, data: ByteArray?)fun getData(key: String?): ByteArray?有趣的删除(键:字符串?)}私有内部类 PasswordStorageHelperSDK16 : PasswordStorageInterface {私有变量首选项:SharedPreferences?= 空覆盖乐趣初始化(上下文:上下文?):布尔{首选项 = 上下文?.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)返回真}覆盖 fun setData(key: String?, data: ByteArray?) {if (data == null) 返回val 编辑器 = 首选项?.edit()editor?.putString(key, Base64.encodeToString(data, Base64.DEFAULT))编辑器?.apply()}覆盖 fun getData(key: String?): ByteArray?{val res =preferences?.getString(key, null) ?: return null返回 Base64.decode(res, Base64.DEFAULT)}覆盖乐趣删除(键:字符串?){val 编辑器 = 首选项?.edit()编辑器? .remove(键)编辑器?.apply()}}私有内部类 PasswordStorageHelperSDK18 : PasswordStorageInterface {private val KEY_ALGORITHM_RSA: String = "RSA";private val KEYSTORE_PROVIDER_ANDROID_KEYSTORE: String = "AndroidKeyStore";private val RSA_ECB_PKCS1_PADDING: String = "RSA/ECB/PKCS1Padding";私有变量首选项:SharedPreferences?= 空;私有变量别名:字符串?= 空;@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)覆盖乐趣初始化(上下文:上下文?):布尔{首选项 = 上下文?.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);alias = context?.getString(R.string.app_package);val ks:密钥库?尝试 {ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);//使用null加载带有默认参数的Keystore.ks?.load(null);//检查私钥和公钥是否已经存在.如果是这样,我们不需要再次生成它们val privateKey:密钥?= ks?.getKey(alias, null);if (privateKey != null && ks.getCertificate(alias) != null) {val publicKey:公钥?= ks.getCertificate(alias).publicKey;如果(公钥!= null){//所有键都可用.返回真;}}} 捕获(例如:异常){返回假;}//创建一个开始时间和结束时间,为即将成为的密钥对的有效范围//生成.val start = GregorianCalendar();val end = GregorianCalendar();end.add(Calendar.YEAR, 10);//指定将传递给 KeyPairGenerator 的参数对象val 规范:算法参数规范?如果 (Build.VERSION.SDK_INT < 23) {规格 = 上下文?.let {android.security.KeyPairGeneratorSpec.Builder(it)//Alias - 是您的 KeyPair 的密钥,将来可以从 Keystore 获取它..setAlias(别名?:")//用于生成对的自签名证书的主题.setSubject(X500Principal(CN=$alias"))//用于生成对的自签名证书的序列号..setSerialNumber(BigInteger.valueOf(1337))//生成对的有效日期范围..setStartDate(start.time).setEndDate(end.time).建造()};} 别的 {spec = KeyGenParameterSpec.Builder(别名?:",KeyProperties.PURPOSE_DECRYPT).setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1).建造();}//使用预期算法初始化密钥对生成器(在本例中为 RSA//和密钥库.此示例使用 AndroidKeyStore.val kpGenerator:密钥对生成器尝试 {kpGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER_ANDROID_KEYSTORE);kpGenerator.initialize(spec);//生成私钥/公钥kpGenerator.generateKeyPair();} catch (e: 异常) {当 (e) {是 NoSuchAlgorithmException,是 InvalidAlgorithmParameterException,是 NoSuchProviderException ->{尝试 {ks?.deleteEntry(别名)} 捕捉(e1:异常){//忽略这里的任何错误}}}}//检查设备是否支持硬件支持的密钥库尝试 {var isHardwareBackedKeystoreSupported:布尔值?= 空isHardwareBackedKeystoreSupported = if (Build.VERSION.SDK_INT <23) {KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA)} 别的 {val privateKey: Key = ks.getKey(alias, null)//KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA)val keyFactory: KeyFactory = KeyFactory.getInstance(privateKey.algorithm, AndroidKeyStore")val keyInfo: KeyInfo = keyFactory.getKeySpec(privateKey, KeyInfo::class.java)keyInfo.isInsideSecureHardware}Log.d(tag, "Hardware-Backed Keystore Supported: $isHardwareBackedKeystoreSupported");} catch (e: 异常) {//KeyStoreException |NoSuchAlgorithmException |UnrecoverableKeyException |InvalidKeySpecException |NoSuchProviderException e}返回真;}覆盖 fun setData(key: String?, data: ByteArray?) {var ks:密钥库?= 空;尝试 {ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);ks.load(null);if (ks.getCertificate(alias) =​​= null) return;val publicKey:公钥?= ks.getCertificate(alias).publicKey;如果(公钥==空){Log.d(tag, "Error: Public key is not found in Keystore");返回;}val 值:String = encrypt(publicKey, data);val 编辑器:SharedPreferences.Editor?= 偏好?.edit();editor?.putString(key, value);编辑器?.apply();} catch (e: 异常) {当 (e) {是 NoSuchAlgorithmException,是 InvalidKeyException,是 NoSuchPaddingException,是 IllegalBlockSizeException,是 BadPaddingException,是 NoSuchProviderException,是 InvalidKeySpecException,是 KeyStoreException,是 CertificateException,是 IOException ->{尝试 {ks?.deleteEntry(别名)} 捕捉(e1:异常){//忽略这里的任何错误}}}}}覆盖 fun getData(key: String?): ByteArray?{var ks:密钥库?= 空;尝试 {ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);ks.load(null);val privateKey: Key = ks.getKey(alias, null);返回解密(私钥,首选项?.getString(键,空));} catch (e: 异常) {//KeyStoreException |NoSuchAlgorithmException |证书异常 |IO异常//|UnrecoverableEntryException |InvalidKeyException |NoSuchPaddingException//|非法块大小异常 |BadPaddingException |NoSuchProviderException尝试 {ks?.deleteEntry(别名);} 捕捉(e1:异常){//忽略这里的任何错误}}返回空;}覆盖乐趣删除(键:字符串?){val 编辑器:SharedPreferences.Editor?= 偏好?.edit();编辑器?.删除(键);编辑器?.apply();}私人乐趣加密(加密密钥:公钥,数据:字节数组?):字符串{val 密码:Cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);cipher.init(Cipher.ENCRYPT_MODE,encryptionKey);val 加密:ByteArray = cipher.doFinal(data);返回 Base64.encodeToString(encrypted, Base64.DEFAULT);}私人乐趣解密(解密密钥:密钥,加密数据:字符串?):字节数组?{if (encryptedData == null) 返回 null;val encryptedBuffer: ByteArray = Base64.decode(encryptedData, Base64.DEFAULT);val 密码:Cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);cipher.init(Cipher.DECRYPT_MODE,decryptionKey);返回 cipher.doFinal(encryptedBuffer);}}}

I'm working on an app that needs to be quite secure, so we want to use the Android Keystore to help make certain things, like accessTokens to the backend, safer and more secure.

I've tried implementing some of it, and it seems like it works, but I worked mainly off of examples, primarily Securely Storing Keys in Android Keystore

Here is some of my code:

object AndroidKeyStore {
const val ANDROID_KEY_STORE = "AndroidKeyStore"
const val AES_MODE = "AES/GCM/NoPadding"
private var iv: ByteArray = byteArrayOf(55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44)

//IS IT SAFE TO HAVE THIS HERE?
const val SECRET_ALIAS = "TEST"

private fun generateSecretKey(keyAlias: String): SecretKey {
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
    val spec = KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setRandomizedEncryptionRequired(false)
            .build()

    keyGenerator.init(spec)
    return keyGenerator.generateKey()
}

private fun getSecretKey(keyAlias: String): SecretKey {
    val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) }
    if (keyStore.getEntry(keyAlias, null) != null) {
        val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry
        return secretKeyEntry.secretKey ?: generateSecretKey(keyAlias)
    }
    return generateSecretKey(keyAlias)
}

fun encrypt(data: String): String {
    val cipher = Cipher.getInstance(AES_MODE)
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(SECRET_ALIAS), GCMParameterSpec(128, iv))
    iv = cipher.iv
    val encodedBytes = cipher.doFinal(data.toByteArray())
    return Base64.encodeToString(encodedBytes, Base64.NO_WRAP)
}

fun decrypt(encrypted: String): String {
    val cipher = Cipher.getInstance(AES_MODE)
    val spec = GCMParameterSpec(128, iv)
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(SECRET_ALIAS), spec)
    val encodedBytes = Base64.decode(encrypted, Base64.NO_WRAP)
    val decoded = cipher.doFinal(encodedBytes)
    return String(decoded, Charsets.UTF_8)
}

}

This is my keystore code, that seems like it encrypts/decrypts stuff okay. But I have a hard time understanding exactly what the Secret alias is and where to put it, safely.

At the same time, I'm also using a secure preference store. So my idea was, to keep the accessToken in the secure preference store, but encrypt it with this keystore code.

Here is the code for the Secure pref store.

object SecurePreferenceUtils {

enum class SecurePreferenceKeys {
    AccessToken, Test
}

fun putString(key: SecurePreferenceKeys, value: String) {
    SecurePrefs.securePreferences.edit().putString(key.name, value).commit()
}

fun getString(key: SecurePreferenceKeys, defaultValue: String): String {
    return SecurePrefs.securePreferences.getString(key.name, defaultValue) ?: ""
}

object SecurePrefs {
lateinit var securePreferences: SecurePreferences
}

In the end, I would like to be able to do like this when I get the accessToken from the backend.

SecurePreferenceUtils.putString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt(""))

val token = AndroidKeyStore.decrypt(SecurePreferenceUtils.getString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt("")))

Hopefully I make some sort of sense on where I want to go, I'm just not quite sure I'm on the right path here.

Any help is appreciated!

解决方案

alias is unique to one key, if you want to store username and password, you need 2 unique aliases

Here I'm getting it from resources xml file It is any unique string value

<resources>
    <string name="app_name">Data_Persistent</string>
    <string name="app_package">com.aprorit.keystoreexample</string>
</resources>

If SDK is less than 18, here I'm using shared preferences since Keystore available 18 upward. And decoding to base64 and store it

If SDK is 18 and above, use Keystore

This is how you access it

private const val PASSWORD_KEY = "password"
    private val passwordStorage: PasswordStorageHelper = PasswordStorageHelper(context)

    fun savePassword(password: String) {
        passwordStorage.setData(PASSWORD_KEY, password.toByteArray())
    }

    fun getPassword() : String {
        return String((passwordStorage.getData(PASSWORD_KEY) ?: ByteArray(0)))
    }

    fun removePassword() {
        passwordStorage.remove(PASSWORD_KEY)
    }

This is the PasswordStorageHelper class that takes care of pretty much everything

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.security.KeyChain
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyInfo
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import androidx.annotation.RequiresApi
import com.example.data_persistent.R
import java.io.IOException
import java.math.BigInteger
import java.security.*
import java.security.cert.CertificateException
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.InvalidKeySpecException
import java.util.*
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import javax.security.auth.x500.X500Principal


class PasswordStorageHelper(context: Context) {
    private val tag = "PasswordStorageHelper"

    private val PREFS_NAME = "SecureData"

    private var passwordStorage: PasswordStorageInterface?

    init {
        passwordStorage = if (Build.VERSION.SDK_INT < 18) {
            PasswordStorageHelperSDK16();
        } else {
            PasswordStorageHelperSDK18();
        }

        var isInitialized: Boolean? = false;

        try {
            isInitialized = passwordStorage?.init(context);
        } catch (ex: Exception) {
            Log.e(tag, "PasswordStorage initialisation error:" + ex.message, ex);
        }

        if (isInitialized != true && passwordStorage is PasswordStorageHelperSDK18) {
            passwordStorage = PasswordStorageHelperSDK16();
            passwordStorage?.init(context);
        }
    }


    fun setData(key: String?, data: ByteArray?) {
        passwordStorage?.setData(key!!, data ?: ByteArray(0))
    }

    fun getData(key: String?): ByteArray? {
        return passwordStorage?.getData(key ?: "")
    }

    fun remove(key: String?) {
        passwordStorage?.remove(key ?: "")
    }

    private interface PasswordStorageInterface {
        fun init(context: Context?): Boolean
        fun setData(key: String?, data: ByteArray?)
        fun getData(key: String?): ByteArray?
        fun remove(key: String?)
    }

    private inner class PasswordStorageHelperSDK16 : PasswordStorageInterface {
        private var preferences: SharedPreferences? = null

        override fun init(context: Context?): Boolean {
            preferences = context?.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
            return true
        }

        override fun setData(key: String?, data: ByteArray?) {
            if (data == null) return
            val editor = preferences?.edit()
            editor?.putString(key, Base64.encodeToString(data, Base64.DEFAULT))
            editor?.apply()
        }

        override fun getData(key: String?): ByteArray? {
            val res = preferences?.getString(key, null) ?: return null
            return Base64.decode(res, Base64.DEFAULT)
        }

        override fun remove(key: String?) {
            val editor = preferences?.edit()
            editor?.remove(key)
            editor?.apply()
        }
    }


    private inner class PasswordStorageHelperSDK18 : PasswordStorageInterface {

        private val KEY_ALGORITHM_RSA: String = "RSA";

        private val KEYSTORE_PROVIDER_ANDROID_KEYSTORE: String = "AndroidKeyStore";
        private val RSA_ECB_PKCS1_PADDING: String = "RSA/ECB/PKCS1Padding";

        private var preferences: SharedPreferences? = null;
        private var alias: String? = null;

        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        override fun init(context: Context?): Boolean {
            preferences = context?.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
            alias = context?.getString(R.string.app_package);

            val ks: KeyStore?

            try {
                ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);

                //Use null to load Keystore with default parameters.
                ks?.load(null);

                // Check if Private and Public already keys exists. If so we don't need to generate them again
                val privateKey: Key? = ks?.getKey(alias, null);
                if (privateKey != null && ks.getCertificate(alias) != null) {
                    val publicKey: PublicKey? = ks.getCertificate(alias).publicKey;
                    if (publicKey != null) {
                        // All keys are available.
                        return true;
                    }
                }
            } catch (ex: Exception) {
                return false;
            }

            // Create a start and end time, for the validity range of the key pair that's about to be
            // generated.
            val start = GregorianCalendar();
            val end = GregorianCalendar();
            end.add(Calendar.YEAR, 10);

            // Specify the parameters object which will be passed to KeyPairGenerator
            val spec: AlgorithmParameterSpec?
            if (Build.VERSION.SDK_INT < 23) {
                spec = context?.let {
                    android.security.KeyPairGeneratorSpec.Builder(it)
                            // Alias - is a key for your KeyPair, to obtain it from Keystore in future.
                            .setAlias(alias ?: "")
                            // The subject used for the self-signed certificate of the generated pair
                            .setSubject(X500Principal("CN=$alias"))
                            // The serial number used for the self-signed certificate of the generated pair.
                            .setSerialNumber(BigInteger.valueOf(1337))
                            // Date range of validity for the generated pair.
                            .setStartDate(start.time).setEndDate(end.time)
                            .build()
                };
            } else {
                spec = KeyGenParameterSpec.Builder(alias ?: "", KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                        .build();
            }

            // Initialize a KeyPair generator using the the intended algorithm (in this example, RSA
            // and the KeyStore. This example uses the AndroidKeyStore.
            val kpGenerator: KeyPairGenerator
            try {
                kpGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
                kpGenerator.initialize(spec);
                // Generate private/public keys
                kpGenerator.generateKeyPair();
            } catch (e: Exception) {
                when (e) {
                    is NoSuchAlgorithmException, is InvalidAlgorithmParameterException, is NoSuchProviderException -> {
                        try {
                            ks?.deleteEntry(alias)
                        } catch (e1: Exception) {
                            // Just ignore any errors here
                        }
                    }
                }

            }

            // Check if device support Hardware-backed keystore
            try {
                var isHardwareBackedKeystoreSupported: Boolean? = null

                isHardwareBackedKeystoreSupported = if (Build.VERSION.SDK_INT < 23) {
                    KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA)
                } else {
                    val privateKey: Key = ks.getKey(alias, null)
                    //KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA)
                    val keyFactory: KeyFactory = KeyFactory.getInstance(privateKey.algorithm, "AndroidKeyStore")
                    val keyInfo: KeyInfo = keyFactory.getKeySpec(privateKey, KeyInfo::class.java)
                    keyInfo.isInsideSecureHardware
                }
                Log.d(tag, "Hardware-Backed Keystore Supported: $isHardwareBackedKeystoreSupported");
            } catch (e: Exception) {
                //KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | InvalidKeySpecException | NoSuchProviderException e
            }

            return true;
        }

        override fun setData(key: String?, data: ByteArray?) {
            var ks: KeyStore? = null;
            try {
                ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);

                ks.load(null);
                if (ks.getCertificate(alias) == null) return;

                val publicKey: PublicKey? = ks.getCertificate(alias).publicKey;

                if (publicKey == null) {
                    Log.d(tag, "Error: Public key was not found in Keystore");
                    return;
                }

                val value: String = encrypt(publicKey, data);

                val editor: SharedPreferences.Editor? = preferences?.edit();
                editor?.putString(key, value);
                editor?.apply();
            } catch (e: Exception) {
                when (e) {
                    is NoSuchAlgorithmException, is InvalidKeyException, is NoSuchPaddingException,
                    is IllegalBlockSizeException, is BadPaddingException, is NoSuchProviderException,
                    is InvalidKeySpecException, is KeyStoreException, is CertificateException, is IOException -> {

                        try {
                            ks?.deleteEntry(alias)
                        } catch (e1: Exception) {
                            // Just ignore any errors here
                        }
                    }
                }
            }
        }


        override fun getData(key: String?): ByteArray? {
            var ks: KeyStore? = null;
            try {
                ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
                ks.load(null);
                val privateKey: Key = ks.getKey(alias, null);
                return decrypt(privateKey, preferences?.getString(key, null));
            } catch (e: Exception) {
                //KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException
                // | UnrecoverableEntryException | InvalidKeyException | NoSuchPaddingException
                // | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
                try {
                    ks?.deleteEntry(alias);
                } catch (e1: Exception) {
                    // Just ignore any errors here
                }
            }
            return null;
        }


        override fun remove(key: String?) {
            val editor: SharedPreferences.Editor? = preferences?.edit();
            editor?.remove(key);
            editor?.apply();
        }

        private fun encrypt(encryptionKey: PublicKey, data: ByteArray?): String {
            val cipher: Cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
            val encrypted: ByteArray = cipher.doFinal(data);
            return Base64.encodeToString(encrypted, Base64.DEFAULT);
        }

        private fun decrypt(decryptionKey: Key, encryptedData: String?): ByteArray? {
            if (encryptedData == null) return null;
            val encryptedBuffer: ByteArray = Base64.decode(encryptedData, Base64.DEFAULT);
            val cipher: Cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
            return cipher.doFinal(encryptedBuffer);
        }

    }
}

这篇关于kotlin 中的 Android 密钥库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆