我们都知道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)