话是这麽说,但要明白即使我们将密码加密储存了,但在使用过程中依旧会有暴露的风险。因此还是会建议在使用这个App的时候不要使用本尊帐号(这也是我没做推文功能的原因之一),并且定期更换密码,除此之外平时手机的使用习惯还是要谨慎为上!
Android keystore system是Android中用来储存加密金钥的系统,自Android 7.0起,在Android Compatibility Definition Document中已将几种演算法的金钥储存至Hardware Backed Keystore订为MUST have:
MUST have hardware backed implementations of RSA, AES, ECDSA and HMAC cryptographic algorithms and MD5, SHA1, SHA-2 Family hash functions to properly support the Android Keystore system's supported algorithms.
因此以目前来说使用Keystore System来储存我们的金钥是相对安全的做法。
以我们的使用情境来说,需要加密的资料只有密码,因此资料量不大。在这情况下我是选择以安全性为主,因此直接使用RSA演算法做加解密。
以下产生金钥方法为API 23以上的做法,兼容低版本的部分可参考文末的参考文章。
class KeystoreUtil {
private val keyStoreProvider = "AndroidKeyStore"
private val alias = "ALIAS_CA"
private val keystore: KeyStore = KeyStore.getInstance(keyStoreProvider)
init {
keystore.load(null)
if (!keystore.containsAlias(alias)) {
genRSAKey()
}
}
private fun genRSAKey() {
val keyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, keyStoreProvider)
val keyGenParameterSpec = KeyGenParameterSpec
.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build()
keyPairGenerator.initialize(keyGenParameterSpec)
keyPairGenerator.generateKeyPair()
}
// ...
}
class KeystoreUtil {
// ...
public fun encrypt(plaintext: String): String {
val publicKey = keystore.getCertificate(alias).publicKey
val cipher = Cipher.getInstance(rsaMode)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(plaintext.toByteArray()).toHexString()
}
// ...
}
toHexString是另外做的扩展函数,将加密後的ByteArray转为Hex字串:
fun ByteArray.toHexString(): String =
joinToString(separator = "") { byte ->
"%02x".format(byte)
}
class KeystoreUtil {
// ...
public fun decrypt(encryptedText: String): String {
val privateKey = keystore.getKey(alias, null) as PrivateKey
val cipher = Cipher.getInstance(rsaMode)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
return cipher.doFinal(encryptedText.hexToByteArray()).toString(StandardCharsets.UTF_8)
}
// ...
}
hexToByteArray则是对应的将Hex字串转回ByteArray的方法:
fun String.hexToByteArray(): ByteArray =
chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
另外注意解密回来後的ByteArray在转回String时有带入StandardCharsets.UTF_8,这是因为Kotlin String预设的toByteArray中有预设指定的Charset Type。
public inline fun String.toByteArray(charset: Charset = Charsets.UTF_8): ByteArray =
(this as java.lang.String).getBytes(charset)
KeystoreUtil完成後就可以直接把昨天存取密码的位置做更换了。
class LoginFragment : Fragment() {
// ...
private val keystoreUtil = KeystoreUtil()
// ...
}
// ...
if (binding.savePwd.isChecked) {
editor.putString(PREF_FIELD_PWD, keystoreUtil.encrypt(pwd))
}
// ...
// ...
val encryptedPwd = preferences.getString(PREF_FIELD_PWD, null)
if (!encryptedPwd.isNullOrBlank()) {
binding.pwdInput.setText(keystoreUtil.decrypt(encryptedPwd))
binding.savePwd.isChecked = true
}
// ...
以下文章使用非对称式演算法加密金钥、对称式演算法加密内文的方法是较为兼顾效能与安全性的方法。
使用Android KeyStore 储存敏感性资料
>>: [Python 爬虫这样学,一定是大拇指拉!] DAY29 - 实战演练:自制进度条 Progress Bar
昨天介绍了在Dart中非同步的基本概念,今天就要来讲到如何简单的控制非同步操作。 Future Fu...
串接地图 JavaScript API 中虽然相较起来难度较高,不过官方文件写的也很简单易懂。 使用...
前言 过年爽爽放,该回来复习复习拉WW,大家新年快乐 今天原本是要来练习以前都没接触过的galler...
我们继续透过 LeetCode #66 Plus One 来实际感受解决问题的过程 ( 题目连结 )...
DE2_115(DAY2)用niosii和switch还有NiosII console去控制板子上的...