Android SharedPreferences加密存储

我们都知道sharedPreferences是将信息以xml文件格式的键值对存储在应用的对应的目录下。但是这个目录本是不够安全,这也就导致了我们存储的信息就不够安全。因此我们需要对信息进行加密存储。
即使是加密存储,秘钥的管理也十分重要,如何才能让秘钥不被外界获取,这里我们使用Android系统提供的KeyStore来提供秘钥,最大程度保证安全性。

object AppKeyStore {
    private var sApplicationContext: Context? = null
    private var sSystemKeyStoreMasterKeyAlias: String? = null
    private var sAppKeyStorePreferencesName: String? = null
    private const val ANDROID_KEY_STORE = "AndroidKeyStore"
    private const val KEY_COMPATIBLE = "compatible"
    private const val MASTER_KEY_BIT_SIZE = 256
    private const val MASTER_KEY_ALGORITHM = "AES"
    private const val MASTER_CIPHER_GCM_TRANSFORMATION = "AES/GCM/NoPadding"
    private const val MASTER_CIPHER_GCM_TAG_BIT_LENGTH = 128
    private const val MASTER_CIPHER_GCM_IV_BIT_SIZE = 96
    private const val TAG = "AppKeyStore"
    private var sDebugLog = false

    @JvmOverloads
    fun init(
        context: Context,
        systemKeyStoreMasterKeyAlias: String = "app_key_store_master_key",
        appKeyStorePreferenceName: String = "app_key_store"
    ) {
        sApplicationContext = context.applicationContext
        sSystemKeyStoreMasterKeyAlias = systemKeyStoreMasterKeyAlias
        sAppKeyStorePreferencesName = appKeyStorePreferenceName
    }

    fun setDebugLog(debugLog: Boolean) {
        sDebugLog = debugLog
    }

  
    fun getOrCreateSecretKey(
        keyAlias: String,
        algorithm: String,
        keySizeInBits: Int
    ): SecretKey {
        checkInit()
        require(keySizeInBits % 8 == 0) { "key bit size must be multiple of 8" }
        val masterKey = masterKey
        val preferences = keyStorePreferences
        val encryptKeyAlias: String
        encryptKeyAlias = try {
            encryptKeyAlias(masterKey, keyAlias)
        } catch (ex: GeneralSecurityException) {
            throw KeyStoreUnavailableException(
                "Failed to encrypt key alias, so unable to recover secret key from storage",
                ex
            )
        }
        val keyData = preferences.getString(encryptKeyAlias, null)
        if (!TextUtils.isEmpty(keyData)) {
            try {
                return decryptSecretKey(masterKey, keyData!!, algorithm)
            } catch (ex: GeneralSecurityException) {
                Log.w(
                    TAG,
                    "decrypt key fail, create new key instead: $keyAlias"
                )
            }
        }
        val randomBytes: ByteArray = CryptoUtil.randomBytes(keySizeInBits / 8)
        val secretKey: SecretKey =
            SecretKeySpec(randomBytes, algorithm)
        return try {
            val encrypted = encryptSecretKey(masterKey, secretKey)
            preferences.edit().putString(encryptKeyAlias, encrypted).apply()
            secretKey
        } catch (ex: GeneralSecurityException) {
            throw KeyStoreUnavailableException(
                "Failed to encrypt the created secret key, so unable to persist it to storage",
                ex
            )
        }
    }

    @get:TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
    private val masterKey: SecretKey
        private get() {
            val preferences = keyStorePreferences
            if (preferences.getBoolean(KEY_COMPATIBLE, false)) {
                if (sDebugLog) {
                    Log.d(TAG, "get MasterKey compatible flag")
                }
                return masterKeyCompat
            }
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                if (sDebugLog) {
                    Log.d(TAG, "get MasterKey below Marshmallow")
                }
                preferences.edit().putBoolean(KEY_COMPATIBLE, true).apply()
                return masterKeyCompat
            }
            return try {
                getMasterKeyByKeyStore(sSystemKeyStoreMasterKeyAlias)
            } catch (ex: GeneralSecurityException) {
                Log.w(
                    TAG,
                    "get MasterKey in keystore error, fallback to compat: $ex"
                )
                preferences.edit().putBoolean(KEY_COMPATIBLE, true).apply()
                masterKeyCompat
            } catch (ex: IOException) {
                Log.w(
                    TAG,
                    "get MasterKey in keystore error, fallback to compat: $ex"
                )
                preferences.edit().putBoolean(KEY_COMPATIBLE, true).apply()
                masterKeyCompat
            }
        }

    @TargetApi(Build.VERSION_CODES.M)
    @Throws(GeneralSecurityException::class, IOException::class)
    private fun getMasterKeyByKeyStore(masterKeyAlias: String?): SecretKey {
        val keyStore =
            KeyStore.getInstance(ANDROID_KEY_STORE)
        keyStore.load(null)
        if (keyStore.containsAlias(masterKeyAlias)) {
            val entry = keyStore.getEntry(masterKeyAlias, null)
            if (entry is KeyStore.SecretKeyEntry) {
                if (sDebugLog) {
                    Log.d(TAG, "master key already exist")
                }
                return entry.secretKey
            }
            keyStore.deleteEntry(masterKeyAlias)
            Log.w(TAG, "master key type not match, delete it to recreate")
        }
        if (sDebugLog) {
            Log.d(TAG, "create master key in KeyStore")
        }
        val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES,
            ANDROID_KEY_STORE
        )
        val spec = KeyGenParameterSpec.Builder(
            masterKeyAlias!!,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(MASTER_KEY_BIT_SIZE)
            .setRandomizedEncryptionRequired(false) // need to init GCMParameterSpec
            .build()
        keyGenerator.init(spec)
        return keyGenerator.generateKey()
    }

   
    private val masterKeyCompat: SecretKey
        private get() {
            @SuppressLint("HardwareIds") val ids =
                Settings.Secure.getString(
                    sApplicationContext!!.contentResolver,
                    Settings.Secure.ANDROID_ID
                ).toByteArray(StandardCharsets.UTF_8)
            val key =
                Arrays.copyOf(ids, MASTER_KEY_BIT_SIZE / 8)
            return SecretKeySpec(key, MASTER_KEY_ALGORITHM)
        }

    @Throws(GeneralSecurityException::class)
    private fun encryptSecretKey(
        masterKey: SecretKey,
        key: SecretKey
    ): String {
        val iv: ByteArray =
            CryptoUtil.randomBytes(MASTER_CIPHER_GCM_IV_BIT_SIZE / 8)
        val encrypted = encrypt(masterKey, key.encoded, iv)
        return CiphertextIv(encrypted, iv).toString()
    }

    @Throws(GeneralSecurityException::class)
    private fun decryptSecretKey(
        masterKey: SecretKey,
        encryptedText: String,
        algorithm: String
    ): SecretKey {
        val ciphertextIv = CiphertextIv(encryptedText)
        val decrypted =
            decrypt(masterKey, ciphertextIv.ciphertext, ciphertextIv.iv)
        return SecretKeySpec(decrypted, algorithm)
    }

    @Throws(GeneralSecurityException::class)
    private fun encryptKeyAlias(
        masterKey: SecretKey,
        keyAlias: String
    ): String {
        val encrypted = encrypt(
            masterKey,
            keyAlias.toByteArray(StandardCharsets.UTF_8),
            constantKeyAliasIv
        )
        return Base64.encodeToString(encrypted, Base64.NO_WRAP)
    }

    @Throws(GeneralSecurityException::class)
    private fun decryptKeyAlias(
        masterKey: SecretKey,
        encryptedText: String
    ): String {
        val decoded: ByteArray
        decoded = try {
            Base64.decode(
                encryptedText.toByteArray(StandardCharsets.UTF_8),
                Base64.NO_WRAP
            )
        } catch (ex: IllegalArgumentException) {
            throw GeneralSecurityException("Base64 decode error", ex)
        }
        val decrypted =
            decrypt(masterKey, decoded, constantKeyAliasIv)
        return String(decrypted, StandardCharsets.UTF_8)
    }

    @Throws(GeneralSecurityException::class)
    private fun encrypt(
        key: SecretKey,
        data: ByteArray,
        iv: ByteArray
    ): ByteArray {
        val cipher =
            Cipher.getInstance(MASTER_CIPHER_GCM_TRANSFORMATION)
        val gcmParameterSpec =
            getGcmParameterSpec(iv)
        cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec)
        return cipher.doFinal(data)
    }

    @Throws(GeneralSecurityException::class)
    private fun decrypt(
        key: SecretKey,
        data: ByteArray,
        iv: ByteArray
    ): ByteArray {
        val cipher =
            Cipher.getInstance(MASTER_CIPHER_GCM_TRANSFORMATION)
        val gcmParameterSpec =
            getGcmParameterSpec(iv)
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec)
        return cipher.doFinal(data)
    }

    private fun checkInit() {
        checkNotNull(sApplicationContext) { "Must call AppKeyStore.init() before any operation" }
    }

    private val constantKeyAliasIv: ByteArray
        private get() {
            @SuppressLint("HardwareIds") val ids =
                Settings.Secure.getString(
                    sApplicationContext!!.contentResolver,
                    Settings.Secure.ANDROID_ID
                ).toByteArray(StandardCharsets.UTF_8)
            return Arrays.copyOf(ids, MASTER_CIPHER_GCM_IV_BIT_SIZE / 8)
        }

    private val keyStorePreferences: SharedPreferences
        private get() = sApplicationContext!!.applicationContext.getSharedPreferences(
            sAppKeyStorePreferencesName,
            Context.MODE_PRIVATE
        )

    private fun getGcmParameterSpec(iv: ByteArray): AlgorithmParameterSpec {
        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            IvParameterSpec(iv)
        } else GCMParameterSpec(MASTER_CIPHER_GCM_TAG_BIT_LENGTH, iv)
    }

    class KeyStoreUnavailableException internal constructor(
        message: String?,
        cause: Throwable?
    ) :
        RuntimeException(message, cause)
}

对应的CiphertextIv实体类

public class CiphertextIv {

    private static final String DELIMITER = ":";
    private static final int BASE64_FLAGS = Base64.NO_WRAP;

    public final byte[] ciphertext;
    public final byte[] iv;

    public CiphertextIv(byte[] ciphertext, byte[] iv) {
        this.ciphertext = new byte[ciphertext.length];
        System.arraycopy(ciphertext, 0, this.ciphertext, 0, ciphertext.length);
        this.iv = new byte[iv.length];
        System.arraycopy(iv, 0, this.iv, 0, iv.length);
    }

   
    public CiphertextIv(@NonNull String encodedText) throws IllegalCiphertextIvException {
        String[] parts = encodedText.split(DELIMITER);

        if (parts.length != 2) {
            throw new IllegalCiphertextIvException("CiphertextIv not this format: cipher:iv");
        }

        try {
            ciphertext = Base64.decode(parts[0].getBytes(StandardCharsets.UTF_8), BASE64_FLAGS);
            iv = Base64.decode(parts[1].getBytes(StandardCharsets.UTF_8), BASE64_FLAGS);
        } catch (IllegalArgumentException ex) {
            throw new IllegalCiphertextIvException(ex);
        }
    }

   
    @Override
    public String toString() {
        return Base64.encodeToString(ciphertext, BASE64_FLAGS) + DELIMITER
                + Base64.encodeToString(iv, BASE64_FLAGS);
    }

    private static class IllegalCiphertextIvException extends GeneralSecurityException {
        IllegalCiphertextIvException(String msg) {
            super(msg);
        }

        IllegalCiphertextIvException(Throwable cause) {
            super(cause);
        }
    }
}

通过实现SharedPreferences的接口类完成加密的SharedPreferences 封装。其本身的存储原理并没有改变,数据的存储位置也没有改变。只是我们对存储数据做了加密处理,对数据获取做了解密处理。最大程度上保证了我们数据的安全性。

class SecurePreferences(
    context: Context,
    name: String?,
    secretKey: SecretKey
) :
    SharedPreferences {
    private val mRawPrefs: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
    private val mSecretKey: SecretKey = secretKey
    private val mConstantKeyIv: ByteArray

    override fun getAll(): Map<String?, *> {
        val all = mRawPrefs.all
        val decryptedAll: MutableMap<String?, Any?> =
            HashMap()
        for ((key, value) in all) {
            val decodedKey = decryptKey(key)
            val rawValue = value!!
            if (rawValue is String) {
                decryptedAll[decodedKey] = decryptValue(rawValue)
            } else if (rawValue is Set<*>) {
                val rawSet =
                    rawValue as Set<String>
                val decryptedSet: MutableSet<String?> =
                    HashSet(rawSet.size)
                for (text in rawSet) {
                    decryptedSet.add(decryptValue(text))
                }
                decryptedAll[decodedKey] = decryptedSet
            }
       
        }
        return decryptedAll
    }

    @Nullable
    override fun getString(
        key: String,
        @Nullable defValue: String?
    ): String? {
        val encrypted = mRawPrefs.getString(encryptKey(key), null)
        val value = decryptValue(encrypted)
        return value ?: defValue
    }

    @Nullable
    override fun getStringSet(
        key: String,
        @Nullable defValues: Set<String>?
    ): Set<String?>? {
        val encryptedSet =
            mRawPrefs.getStringSet(encryptKey(key), null)
        var values: MutableSet<String?>? = null
        if (encryptedSet != null) {
            values = HashSet(encryptedSet.size)
            for (text in encryptedSet) {
                values.add(decryptValue(text))
            }
        }
        return values?.toSet() ?: defValues
    }

    override fun getInt(key: String, defValue: Int): Int {
        val encrypted = mRawPrefs.getString(encryptKey(key), null)
        val value = decryptValue(encrypted) ?: return defValue
        return try {
            value.toInt()
        } catch (ex: NumberFormatException) {
            defValue
        }
    }

    override fun getLong(key: String, defValue: Long): Long {
        val encrypted = mRawPrefs.getString(encryptKey(key), null)
        val value = decryptValue(encrypted) ?: return defValue
        return try {
            value.toLong()
        } catch (ex: NumberFormatException) {
            defValue
        }
    }

    override fun getFloat(key: String, defValue: Float): Float {
        val encrypted = mRawPrefs.getString(encryptKey(key), null)
        val value = decryptValue(encrypted) ?: return defValue
        return try {
            value.toFloat()
        } catch (ex: NumberFormatException) {
            defValue
        }
    }

    override fun getBoolean(key: String, defValue: Boolean): Boolean {
        val encrypted = mRawPrefs.getString(encryptKey(key), null)
        val value = decryptValue(encrypted) ?: return defValue
        return try {
            java.lang.Boolean.parseBoolean(value)
        } catch (ex: NumberFormatException) {
            defValue
        }
    }

    override fun contains(key: String): Boolean {
        return mRawPrefs.contains(encryptKey(key))
    }

    override fun edit(): Editor {
        return Editor()
    }

    override fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
        mRawPrefs.registerOnSharedPreferenceChangeListener(listener)
    }

    override fun unregisterOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
        mRawPrefs.unregisterOnSharedPreferenceChangeListener(listener)
    }

    private fun encryptKey(@Nullable text: String): String? {
        return if (TextUtils.isEmpty(text)) {
            text
        } else try {
            val encrypted =
                encrypt(text.toByteArray(StandardCharsets.UTF_8), mConstantKeyIv)
            Base64.encodeToString(
                encrypted,
                BASE64_FLAGS
            )
        } catch (ex: GeneralSecurityException) {
            Log.w(TAG, "encryptKey error:$ex")
            null
        }
    }

    private fun decryptKey(@Nullable text: String): String? {
        return if (TextUtils.isEmpty(text)) {
            text
        } else try {
            val decode = Base64.decode(
                text.toByteArray(StandardCharsets.UTF_8),
                BASE64_FLAGS
            )
            val decrypted = decrypt(decode, mConstantKeyIv)
            String(decrypted, StandardCharsets.UTF_8)
        } catch (ex: GeneralSecurityException) {
            Log.w(TAG, "decryptKey error:$ex")
            null
        } catch (ex: IllegalArgumentException) {
            Log.w(TAG, "decryptKey error:$ex")
            null
        }
    }

    @Nullable
    private fun encryptValue(@Nullable text: String?): String? {
        return if (TextUtils.isEmpty(text)) {
            text
        } else try {
            val iv: ByteArray =
                CryptoUtil.randomBytes(CIPHER_GCM_IV_BIT_SIZE / 8)
            val encrypted =
                encrypt(text!!.toByteArray(StandardCharsets.UTF_8), iv)
            CiphertextIv(encrypted, iv).toString()
        } catch (ex: GeneralSecurityException) {
            Log.w(TAG, "encryptValue error:$ex")
            null
        }
    }

    @Nullable
    private fun decryptValue(@Nullable text: String?): String? {
        return if (TextUtils.isEmpty(text)) {
            text
        } else try {
            val ciphertextIv = CiphertextIv(text!!)
            val decrypted = decrypt(ciphertextIv.ciphertext, ciphertextIv.iv)
            String(decrypted, StandardCharsets.UTF_8)
        } catch (ex: GeneralSecurityException) {
            Log.w(TAG, "decryptValue error:$ex")
            null
        }
    }

    @Throws(GeneralSecurityException::class)
    private fun encrypt(data: ByteArray, iv: ByteArray): ByteArray {
        val cipher =
            Cipher.getInstance(CIPHER_GCM_TRANSFORMATION)
        val gcmParameterSpec =
            getGcmParameterSpec(iv)
        cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, gcmParameterSpec)
        return cipher.doFinal(data)
    }

    @Throws(GeneralSecurityException::class)
    private fun decrypt(data: ByteArray, iv: ByteArray): ByteArray {
        val cipher =
            Cipher.getInstance(CIPHER_GCM_TRANSFORMATION)
        val gcmParameterSpec =
            getGcmParameterSpec(iv)
        cipher.init(Cipher.DECRYPT_MODE, mSecretKey, gcmParameterSpec)
        return cipher.doFinal(data)
    }

    private fun getConstantKeyIv(context: Context): ByteArray {
        @SuppressLint("HardwareIds") val ids =
            Settings.Secure.getString(
                context.contentResolver,
                Settings.Secure.ANDROID_ID
            ).toByteArray(StandardCharsets.UTF_8)
        return Arrays.copyOf(ids, CIPHER_GCM_IV_BIT_SIZE / 8)
    }

    inner class Editor @SuppressLint("CommitPrefEdits") internal constructor() :
        SharedPreferences.Editor {
        private val mEditor: SharedPreferences.Editor
        override fun putString(
            key: String,
            @Nullable value: String?
        ): SharedPreferences.Editor {
            mEditor.putString(encryptKey(key), encryptValue(value))
            return this
        }

        override fun putStringSet(
            key: String,
            @Nullable values: Set<String>?
        ): SharedPreferences.Editor {
            var encryptSet: MutableSet<String?>? = null
            if (values != null) {
                encryptSet = HashSet(values.size)
                for (text in values) {
                    encryptSet.add(encryptValue(text))
                }
            }
            mEditor.putStringSet(encryptKey(key), encryptSet)
            return this
        }

        override fun putInt(key: String, value: Int): SharedPreferences.Editor {
            mEditor.putString(encryptKey(key), encryptValue(Integer.toString(value)))
            return this
        }

        override fun putLong(
            key: String,
            value: Long
        ): SharedPreferences.Editor {
            mEditor.putString(encryptKey(key), encryptValue(java.lang.Long.toString(value)))
            return this
        }

        override fun putFloat(
            key: String,
            value: Float
        ): SharedPreferences.Editor {
            mEditor.putString(encryptKey(key), encryptValue(java.lang.Float.toString(value)))
            return this
        }

        override fun putBoolean(
            key: String,
            value: Boolean
        ): SharedPreferences.Editor {
            mEditor.putString(encryptKey(key), encryptValue(java.lang.Boolean.toString(value)))
            return this
        }

        override fun remove(key: String): SharedPreferences.Editor {
            mEditor.remove(encryptKey(key))
            return this
        }

        override fun clear(): SharedPreferences.Editor {
            mEditor.clear()
            return this
        }

        override fun commit(): Boolean {
            return mEditor.commit()
        }

        override fun apply() {
            mEditor.apply()
        }

        init {
            mEditor = mRawPrefs.edit()
        }
    }

    companion object {
        private const val CIPHER_GCM_TRANSFORMATION = "AES/GCM/NoPadding"
        private const val CIPHER_GCM_TAG_BIT_LENGTH = 128
        private const val CIPHER_GCM_IV_BIT_SIZE = 96
        private const val BASE64_FLAGS = Base64.NO_WRAP
        private const val TAG = "SecurePreferences"
        private fun getGcmParameterSpec(iv: ByteArray): AlgorithmParameterSpec {
            return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                // GCMParameterSpec should work since API 19, but in some device it's not supported (e.g. Google's Android 4.4 emulator).
                // We can use IvParameterSpec instead, it will use the default tag size of 128 bits.
                IvParameterSpec(iv)
            } else GCMParameterSpec(
                CIPHER_GCM_TAG_BIT_LENGTH,
                iv
            )
        }
    }


    init {
        mConstantKeyIv = getConstantKeyIv(context)
    }
}

封装使用:

使用初始化一下KeyStore就好

AppKeyStore.init(this)
AppKeyStore.setDebugLog(BuildConfig.DEBUG)
// 获取SecretKey
val commonAesKey = AppKeyStore.getOrCreateSecretKey(COMMON_AES_KEY_ALIAS,
    COMMON_AES_KEY_ALGORITHM, COMMON_AES_KEY_SIZE_BITS)
// 初始化SecurePreferences
val prefs = SecurePreferences(this, SECURE_PREFERENCES, commonAesKey)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值