应用API密钥迁移至Android Keystore的安全存储技术咨询
Hey there! I get it—having API keys hardcoded in your app is a big red flag, and third-party libraries like Secure Preferences not passing audits is frustrating. Let’s walk through how to properly migrate those keys to Android Keystore, which is the platform’s recommended secure storage solution that should pass most security checks.
First, we’ll create an AES key directly in Android Keystore—this key never leaves the secure hardware (if the device supports it) and can’t be exported, which is why it’s audit-friendly.
fun generateKeystoreKey(keyAlias: String) { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore" ) val keySpec = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) // Authenticated encryption, prevents tampering .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setRandomizedEncryptionRequired(true) // Ensures unique ciphertexts for same plaintext .setUserAuthenticationRequired(false) // Adjust this if you need user auth (fingerprint/PIN) to access the key .build() keyGenerator.init(keySpec) keyGenerator.generateKey() }
Next, we’ll encrypt your existing API key using the Keystore key. We need to store the initialization vector (IV) along with the encrypted data—GCM mode requires this for decryption, and it’s safe to store publicly.
fun encryptApiKey(apiKey: String, keyAlias: String): String { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val secretKey = keyStore.getKey(keyAlias, null) as SecretKey val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, secretKey) val iv = cipher.iv // GCM uses a 12-byte IV by default val encryptedBytes = cipher.doFinal(apiKey.toByteArray(Charsets.UTF_8)) // Combine IV and encrypted data into a single byte array val combinedData = iv + encryptedBytes return Base64.encodeToString(combinedData, Base64.DEFAULT) }
Now save the encrypted key to standard SharedPreferences—since it’s encrypted, even if someone gains access to the preferences file, they can’t decrypt it without the Keystore key.
fun saveEncryptedKey(context: Context, encryptedKey: String) { val prefs = context.getSharedPreferences("AppSecureStorage", Context.MODE_PRIVATE) prefs.edit().putString("ENCRYPTED_API_KEY", encryptedKey).apply() }
When your app needs to call the API, decrypt the key on-the-fly. Make sure to avoid holding the decrypted key in memory longer than necessary.
fun decryptApiKey(encryptedKey: String, keyAlias: String): String { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val secretKey = keyStore.getKey(keyAlias, null) as SecretKey val combinedData = Base64.decode(encryptedKey, Base64.DEFAULT) // Split IV (first 12 bytes) and encrypted data val iv = combinedData.copyOfRange(0, 12) val encryptedBytes = combinedData.copyOfRange(12, combinedData.size) val cipher = Cipher.getInstance("AES/GCM/NoPadding") val spec = GCMParameterSpec(128, iv) // 128-bit authentication tag length cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) val decryptedBytes = cipher.doFinal(encryptedBytes) return String(decryptedBytes, Charsets.UTF_8) }
This is how you’ll move from hardcoded keys to Keystore:
- On your app’s first launch after updating, check if the encrypted key exists in SharedPreferences.
- If it doesn’t, fetch the hardcoded API key (this is a one-time operation!).
- Generate the Keystore key, encrypt the hardcoded key, and save the encrypted version to SharedPreferences.
- In your next app update, completely remove the hardcoded API key from your codebase.
- Never log any part of the API key (encrypted or decrypted) or IV.
- Enable
setUserAuthenticationRequired(true)if your app’s use case allows—this requires the user to authenticate (fingerprint/PIN) before accessing the Keystore key, adding an extra layer of security. - Avoid storing the decrypted key in static variables or passing it around unnecessarily; use it immediately for your API call and then discard it.
- Test on devices with secure hardware (like those with StrongBox) to ensure maximum security—Keystore leverages hardware-backed storage when available.
内容的提问来源于stack exchange,提问作者Perry Hoekstra




