【问题标题】:Android Keystore in kotlinkotlin 中的 Android 密钥库
【发布时间】:2019-10-01 14:44:31
【问题描述】:

我正在开发一个需要非常安全的应用,因此我们希望使用 Android 密钥库来帮助使某些事情变得更安全,例如后端的 accessTokens。

我已经尝试过实现其中的一些,它似乎可以工作,但我主要是根据示例工作,主要是 Securely Storing Keys in Android Keystore

这是我的一些代码:

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)
}

}

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

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

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

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
}

最后,当我从后端获取 accessToken 时,我希望能够这样做。

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

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

希望我对我想去的地方有所了解,我只是不太确定自己是否走在正确的道路上。

感谢任何帮助!

【问题讨论】:

  • 你有没有找到问题的答案?我现在正在处理类似的事情。
  • 我做到了,但我自己弄清楚了。但那是很久以前的事了,我已经不在那家公司工作了,所以我不记得我是怎么解决的了,抱歉。

标签: android security kotlin android-keystore


【解决方案1】:

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

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

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

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

如果 SDK 为 18 及以上,请使用 Keystore

这是您访问它的方式

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)
    }

这是处理几乎所有事情的 PasswordStorageHelper 类

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);
        }

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-24
    相关资源
    最近更新 更多