You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android端用密码与生物识别生成密钥加密本地数据方案咨询

实现双认证(密码+生物识别)的本地数据加密方案

行业里通用的做法是分层加密:用一个临时的主密钥(Data Encryption Key, DEK)加密实际业务数据,再分别用生物识别密钥和密码派生的密钥(Key Encryption Key, KEK)加密这个DEK,把加密后的DEK存在本地私有文件中。这样不管用户用密码还是生物识别认证,只要能解密出DEK,就能解密核心数据。

1. 核心分层逻辑拆解

  • DEK(数据加密密钥):直接加密你的业务数据,不存储在本地,仅在解密时临时生成/解密获取,用完立即从内存清除。
  • KEK1(生物识别密钥):通过AndroidKeyStore生成,绑定生物识别认证,仅在用户完成生物识别后才能用来解密加密后的DEK。
  • KEK2(密码派生密钥):从用户密码通过PBKDF2/Argon2算法派生而来,用来加密DEK,用户输入密码后可派生密钥解密DEK。

2. 代码实现示例

生成主密钥(DEK)

fun generateDataEncryptionKey(): SecretKey {
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    val spec = KeyGenParameterSpec.Builder(
        "data_encryption_key",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setKeySize(256)
        .build()
    keyGenerator.init(spec)
    return keyGenerator.generateKey()
}

生成生物识别绑定密钥(KEK1)

fun generateBiometricKey(): SecretKey {
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    val spec = KeyGenParameterSpec.Builder(
        "biometric_protected_key",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setKeySize(256)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationValidityDurationSeconds(-1) // 每次使用必须重新认证
        .setInvalidatedByBiometricEnrollment(true) // 用户新增/删除指纹时自动失效
        .build()
    keyGenerator.init(spec)
    return keyGenerator.generateKey()
}

从密码派生密钥(KEK2)

fun deriveKeyFromPassword(password: CharArray, salt: ByteArray): SecretKey {
    // 迭代次数建议65536以上,密钥长度256位
    val passwordSpec = PBEKeySpec(password, salt, 65536, 256)
    val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
    val keyBytes = keyFactory.generateSecret(passwordSpec).encoded
    // 清空密码数组,避免内存残留
    Arrays.fill(password, ' ')
    return SecretKeySpec(keyBytes, KeyProperties.KEY_ALGORITHM_AES)
}
// 注意:salt需要随机生成一次后存储在本地私有文件,后续派生密钥必须复用同一salt

加密流程

  1. 生成DEK,用DEK加密业务数据,将加密后的数据+GCM的IV写入本地文件
  2. 用生物识别密钥加密DEK,得到encrypted_dek_bio并写入本地文件
  3. 用密码派生密钥加密DEK,得到encrypted_dek_pass并写入本地文件
  4. 立即清除内存中的DEK

解密流程(生物识别方式)

  1. 调用BiometricPrompt发起生物识别认证
  2. 认证通过后,用生物识别密钥解密encrypted_dek_bio得到DEK
  3. 用DEK+之前存储的IV解密业务数据
  4. 立即清除内存中的DEK

解密流程(密码方式)

  1. 获取用户输入的密码
  2. 读取本地存储的salt,派生得到KEK2
  3. 用KEK2解密encrypted_dek_pass得到DEK
  4. 用DEK+IV解密业务数据
  5. 立即清除内存中的密码、DEK

3. 关键安全细节

  • 内存清理:所有敏感数据(密码、密钥字节数组)用完后必须用Arrays.fill清空,避免被内存dump窃取
  • 存储位置:加密后的DEK、salt必须存在App私有文件目录(context.filesDir),禁止用SharedPreferences/DataStore
  • 密钥轮换:用户修改密码或生物识别信息变更时,需重新生成对应的KEK,并用新KEK重新加密DEK
  • 算法选择:API 30+优先用Argon2替代PBKDF2,前者抗暴力破解能力更强

4. 行业应用参考

像1Password、Bitwarden这类安全应用都是这套逻辑:

  • 用主密钥加密所有用户核心数据
  • 分别用生物识别密钥、设备PIN、用户密码派生密钥来加密主密钥
  • 用户可通过任意一种认证方式解锁主密钥,进而访问数据

内容的提问来源于stack exchange,提问作者Wolf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 03:15:40