如何基于用户自定义密码在Android实现合规SCA认证?
基于应用内自定义密码的Android SCA合规实现方案
核心思路
SCA要求覆盖三类认证因子,我们需要把应用内自定义密码(所知信息)和设备安全硬件(所持物品)绑定,同时预留生物特征认证的扩展能力。核心目标是让私钥始终留在Android安全硬件中,用应用密码作为解锁私钥的前置验证因子,而非直接用密码保护密钥库。
方案一:AndroidKeyStore + 应用密码派生密钥(推荐)
AndroidKeyStore不支持直接用应用密码加载,但可以通过密码派生对称密钥(KEK,密钥加密密钥),结合硬件存储的业务密钥实现安全保护。
实现步骤
- 从用户6位密码派生对称密钥(KEK),用于加密密钥备份(可选)或作为认证凭证
- 在AndroidKeyStore生成EC私钥(完全存于硬件,不可导出)
- 用KEK加密私钥备份(若需跨设备同步),存储到应用私有目录
代码示例:
// 1. 从用户密码派生对称KEK val userPassword = "123456".toCharArray() val salt = SecureRandom.getInstanceStrong().generateSeed(16) val pbkdfSpec = PBEKeySpec(userPassword, salt, 10000, 256) val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val kek = keyFactory.generateSecret(pbkdfSpec).encoded // 2. 在AndroidKeyStore生成硬件存储的EC私钥 val keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore" ) val keyGenSpec = KeyGenParameterSpec.Builder( "sca_ec_key", KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY ) .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) .setUserAuthenticationRequired(false) // 不依赖系统锁屏,改用应用密码验证 .setIsStrongBoxBacked(true) // 优先使用StrongBox硬件(若设备支持) .build() keyPairGenerator.initialize(keyGenSpec) val hardwareKeyPair = keyPairGenerator.generateKeyPair() // 3. 可选:用KEK加密私钥备份(仅当需要跨设备同步时) val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(kek, "AES")) val encryptedPrivateKey = cipher.doFinal(hardwareKeyPair.private.encoded) // 将encryptedPrivateKey、salt存储到应用私有目录
使用流程
- 用户输入6位密码,重新派生KEK
- 验证密码合法性(对比本地存储的加盐哈希值)
- 直接使用AndroidKeyStore中的私钥完成签名/认证操作
- 可选:替换密码验证为BiometricPrompt,满足生物特征因子要求
方案二:AndroidKeyStore + 密码绑定的密钥使用限制
生成硬件密钥时,设置每次使用都需前置验证,将应用密码作为验证环节,确保只有密码正确才能调用密钥。
代码示例:
// 生成绑定认证要求的硬件密钥 val keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore" ) val keyGenSpec = KeyGenParameterSpec.Builder( "sca_ec_key", KeyProperties.PURPOSE_SIGN ) .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) .setUserAuthenticationRequired(true) .setUserAuthenticationValidityDurationSeconds(0) // 每次使用都需认证 .build() keyPairGenerator.initialize(keyGenSpec) val hardwareKeyPair = keyPairGenerator.generateKeyPair() // 使用密钥前的密码验证逻辑 fun signWithKey(data: ByteArray, inputPassword: String): ByteArray { // 验证输入密码与存储的加盐哈希是否匹配 if (!validatePassword(inputPassword)) { throw IllegalArgumentException("Invalid password") } // 验证通过后调用硬件密钥签名 val signature = Signature.getInstance("SHA256withECDSA") signature.initSign(hardwareKeyPair.private) signature.update(data) return signature.sign() } // 密码存储与验证:仅存加盐哈希,不存明文 private fun validatePassword(input: String): Boolean { val storedHash = getStoredPasswordHash() // 从SharedPreferences获取 val storedSalt = getStoredSalt() val inputHash = hashPassword(input.toCharArray(), storedSalt) return MessageDigest.isEqual(storedHash, inputHash) }
此方案中私钥完全隔离在硬件中,无法导出,同时应用密码作为前置验证,可无缝替换为生物特征认证,完全覆盖SCA三类因子。
方案三:自定义密钥库的合规优化(不推荐)
若必须使用自定义密钥库,需通过以下方式提升安全性以满足SCA要求:
- 用AndroidKeyStore生成的对称密钥加密自定义密钥库文件
- 禁止私钥导出,仅在内存中完成加密/签名操作
- 增加设备安全检查(如检测root状态、验证系统完整性)
但该方案私钥不在硬件中存储,安全性弱于前两种,不符合SCA对"所持物品"的强安全要求,仅作为极端场景下的备选。
SCA合规性确认
确保实现覆盖三类认证因子:
- 所知信息:应用内6位密码,独立于系统锁屏的验证逻辑
- 所持物品:密钥存储在AndroidKeyStore(StrongBox/TEE),无法被导出到设备外部
- 生物特征:可将密码验证环节替换为BiometricPrompt,支持指纹/面容识别
内容的提问来源于stack exchange,提问作者okarakose
相关产品推荐
相关产品推荐

