Android Studio中如何安全存储SHA-1等证书哈希值
Android敏感信息(SHA哈希、API密钥)安全存储方案
为什么values.xml不安全
把google_api、Firebase配置或SHA哈希这类敏感信息放在values.xml里,会直接被打包进APK,通过反编译工具就能轻松提取,完全没有安全性可言,必须改用加密存储方案。
核心方案:Android Keystore系统
Android Keystore是系统提供的安全密钥管理服务,生成的密钥会被存储在设备的安全硬件(如TEE、Secure Element)中,密钥本身不会离开安全环境,无法被提取,非常适合用来加密敏感数据。
实现步骤
1. 创建Keystore密钥对
首先要在Keystore中生成一对RSA密钥,公钥用于加密数据,私钥用于解密(私钥由系统托管,无法直接获取)。
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import java.security.KeyPairGenerator import java.security.KeyStore import java.security.spec.MGF1ParameterSpec import javax.crypto.spec.OAEPParameterSpec import javax.crypto.spec.PSource private const val KEYSTORE_ALIAS = "my_app_secure_key" private const val KEYSTORE_PROVIDER = "AndroidKeyStore" fun createKeystoreKeyPair() { val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) } // 如果密钥已存在,无需重复创建 if (!keyStore.containsAlias(KEYSTORE_ALIAS)) { val keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER ) val keyGenSpec = KeyGenParameterSpec.Builder( KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_ECB) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) .setDigests(KeyProperties.DIGEST_SHA256) .build() keyPairGenerator.initialize(keyGenSpec) keyPairGenerator.generateKeyPair() } }
2. 加密敏感数据
用生成的公钥对明文敏感数据(如SHA-1、Google API密钥)进行加密,加密后的结果可以转成Base64字符串,存储到SharedPreferences或其他本地存储中。
import android.util.Base64 import java.security.KeyStore import javax.crypto.Cipher fun encryptData(plainText: String): String { val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) } val publicKey = keyStore.getCertificate(KEYSTORE_ALIAS).publicKey val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding").apply { init(Cipher.ENCRYPT_MODE, publicKey, OAEPParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT )) } val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8)) return Base64.encodeToString(encryptedBytes, Base64.DEFAULT) }
3. 解密数据
在需要使用敏感数据时,用Keystore中的私钥解密加密后的字符串,得到明文后仅在内存中使用,不要持久化明文。
fun decryptData(encryptedText: String): String { val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) } val privateKey = keyStore.getKey(KEYSTORE_ALIAS, null) as java.security.PrivateKey val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding").apply { init(Cipher.DECRYPT_MODE, privateKey, OAEPParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT )) } val encryptedBytes = Base64.decode(encryptedText, Base64.DEFAULT) val decryptedBytes = cipher.doFinal(encryptedBytes) return String(decryptedBytes, Charsets.UTF_8) }
4. 替换values.xml的存储流程
- 提前在本地运行加密代码,把SHA-1、Google API密钥、Firebase配置等敏感信息加密成Base64字符串。
- 将加密后的字符串存储到
SharedPreferences(首次启动时写入),或者打包到assets目录的加密配置文件中。 - 在App需要使用这些信息时,调用解密方法获取明文,比如初始化Firebase时:
// 从SharedPreferences获取加密后的Firebase配置 val encryptedFirebaseConfig = sharedPreferences.getString("firebase_config", "") val firebaseConfig = decryptData(encryptedFirebaseConfig) // 解析配置并初始化Firebase
关键注意事项
- 密钥别名
KEYSTORE_ALIAS建议用包名拼接固定字符串,避免和其他应用冲突。 - 仅在App首次启动时创建Keystore密钥对,后续启动直接使用已存在的密钥。
- 解密后的明文仅在内存中使用,不要写入任何持久化存储。
- 对于Android 6.0(API 23)以下的设备,Keystore的加密算法支持有限,建议针对低版本设备添加兼容逻辑,或提示用户升级系统。
内容的提问来源于stack exchange,提问作者Abm
相关产品推荐
相关产品推荐

