使用生物识别API在Android Keystore存储密码的位置疑问
Android Keystore密码存储位置疑问
我希望将密码存储到Android Keystore中,但不清楚加密后的密码实际存储的位置,想确认是应该存在SharedPreference还是File中?
以下是我目前尝试的代码:
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec class MainActivity : AppCompatActivity() { private val KEY_ALIAS = "my_secure_key" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val secretKey = if (keyStore.containsAlias(KEY_ALIAS)) { keyStore.getKey(KEY_ALIAS, null) as SecretKey } else { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore" ) val keyGenParameterSpec = KeyGenParameterSpec.Builder( KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) // Requires biometric authentication .build() keyGenerator.init(keyGenParameterSpec) keyGenerator.generateKey() } val biometricPrompt = BiometricPrompt( this, ContextCompat.getMainExecutor(this), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) val cipher = result.cryptoObject?.cipher if (cipher != null) { val encryptedUsername = cipher.doFinal("my_username".toByteArray()) val encryptedPassword = cipher.doFinal("my_password".toByteArray()) // Store encryptedUsername and encryptedPassword as needed } } override fun onAuthenticationError( errorCode: Int, errString: CharSequence ) { super.onAuthenticationError(errorCode, errString) // Handle authentication errors } override fun onAuthenticationFailed() { super.onAuthenticationFailed() // Handle authentication failures } } ) val cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE ) val secretKeyEntry = keyStore.getEntry(KEY_ALIAS, null) as KeyStore.SecretKeyEntry val secretKey = secretKeyEntry.secretKey val gcmSpec = GCMParameterSpec.Builder() .setKeySize(256) .setNonce(ByteArray(12)) // You need to generate a random nonce for each encryption .build() cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Authenticate to access credentials") .setSubtitle("Using your biometric") .setDescription("Authenticate using your fingerprint to access your credentials.") .setNegativeButtonText("Cancel") .build() biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher)) } }
核心解答
1. Android Keystore的本质作用
Android Keystore不存储你的明文或加密后密码,它只负责安全托管你生成的AES加密密钥(代码中的my_secure_key)。这个密钥由系统底层保护,你的App无法直接导出明文密钥,只能通过授权的Cipher操作调用它,从根源降低密钥泄露风险。
2. 加密后密码的存储选择
加密后的用户名/密码需要你自行选择存储介质,SharedPreference或File都可行:
- SharedPreference:适合存储小体积敏感数据,操作便捷。推荐使用
EncryptedSharedPreferences(AndroidX官方封装),它会自动用Keystore中的密钥加密存储内容,无需手动处理Cipher逻辑,安全性更高。 - File:适合存储较大数据,建议将加密后的字节数组写入App私有目录(
getFilesDir()或getNoBackupFilesDir()),绝对不要存到外部存储(防止被其他App或用户直接访问)。
3. 代码优化关键要点
- 随机Nonce必须存储:GCM模式要求每次加密使用唯一的12字节随机Nonce,你代码中用空字节数组是严重安全隐患。需要生成随机Nonce,并且和加密数据一起存储(解密时必须使用同一个Nonce)。
- 避免重复获取密钥:代码中两次获取
secretKey,可以合并为一次操作,减少冗余。 - 存储逻辑示例:在认证成功后添加SharedPreference存储代码:
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) val cipher = result.cryptoObject?.cipher if (cipher != null) { // 获取Cipher使用的随机Nonce val nonce = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java).nonce val encryptedUsername = cipher.doFinal("my_username".toByteArray()) val encryptedPassword = cipher.doFinal("my_password".toByteArray()) // 存储加密数据和Nonce到私有SharedPreference val securePref = getSharedPreferences("SecureCreds", MODE_PRIVATE) securePref.edit() .putByteArray("encrypted_user", encryptedUsername) .putByteArray("encrypted_pwd", encryptedPassword) .putByteArray("gcm_nonce", nonce) .apply() } }
内容的提问来源于stack exchange,提问作者Abhijit Chakra
相关产品推荐
相关产品推荐

