如何使用 Android Keystore 实现 RSA 密钥对的生成与签名验证?

解读

面试官抛出这道题,核心想验证三件事:

  1. 是否真正动手用过 AndroidKeystore,而不是只背过“KeyStore 是硬件保险箱”这句话;
  2. 对国密合规、国内机型碎片化、TEE/SE 可用性差异有没有体感;
  3. 能否把“生成→使用→验证”闭环讲清楚,并主动说出防重放、防篡改、合规留痕等工程细节。
    回答时切忌只贴官方模板代码,一定要结合国内场景:华为/小米/OPPO 对 StrongBox 支持度不同、部分低端机只有 TEE、国密算法需外挂库、应用市场审计要求密钥使用日志等。

知识点

  1. AndroidKeystore 是系统级密钥仓库,私钥不出安全硬件(TEE/SE),符合国内金融类 App“密钥不离设备”合规要求。
  2. 密钥必须在 KeyGenParameterSpec 里强制指定 PURPOSE_SIGN 和 PURPOSE_VERIFY,并显式设置 digests(SHA256/SHA384),否则在部分国内 ROM 上会因“算法缺失”崩溃。
  3. RSA 签名默认是 RSA PKCS#1 v1.5,国内政务类接口已强制迁移到 RSA-PSS;面试时要能说出如何切换 Signature 实例:“SHA256withRSA/PSS”。
  4. 签名结果通常 Base64 编码后随业务数据一起上传,服务端需记录签名值、签名算法、设备证书序列号,方便后续审计。
  5. 国内主流机型对硬件密钥有“密钥失效”场景:恢复出厂设置、刷机、解锁 Bootloader、用户关闭锁屏密码,都会导致密钥永久失效,App 需有“密钥失效重新注册”的补偿流程。
  6. 性能:TEE 下 RSA 2048 私钥签名一次约 30-40 ms,UI 线程直接调用会掉帧,必须放后台线程;面试官常追问“你怎么发现卡顿”,可答“使用 Systrace 看到 Binder IPC 耗时突刺”。
  7. 安全测评:国内第三方安全实验室(如中国电科、信通院)会检查是否开启 setUserAuthenticationRequired(true) 以及是否绑定生物识别,回答时主动提到“我们通过了金融科技产品认证,密钥必须绑定指纹/3D 人脸”。

答案

分四步落地,代码均基于 Android 9+(API 28+),兼容国内华为、小米、OPPO 主流 ROM。

第一步:生成 RSA 密钥对,强制硬件级,私钥不出 TEE

val kpg = KeyPairGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore"
)
val spec = KeyGenParameterSpec.Builder(
    "demo_rsa", 
    KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256)
    setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
    setKeySize(2048)
    // 国内合规:必须绑定锁屏密码或生物识别
    setUserAuthenticationRequired(true)
    // 防止重放,签名前 30 秒内必须解锁过
    setUserAuthenticationValidityDurationSeconds(30)
    // 显式要求硬件安全等级
    if (Build.VERSION.SDK_INT >= 28) {
        setIsStrongBoxBacked(
            packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
        )
    }
    build()
}
kpg.initialize(spec)
kpg.generateKeyPair()   // 耗时 <200 ms,放后台协程

第二步:使用私钥签名,数据为业务原文

val entry = KeyStore.getInstance("AndroidKeyStore")
    .apply { load(null) }
    .getEntry("demo_rsa", null) as KeyStore.PrivateKeyEntry

val signature = Signature.getInstance("SHA256withRSA/PSS").run {
    initSign(entry.privateKey)
    update(plainBytes)          // plainBytes 为订单号+时间戳+随机数
    sign()
}
// 上传:Base64.encodeToString(signature, Base64.NO_WRAP)

第三步:本地公钥验签(模拟服务端验签)

val publicKey = entry.certificate.publicKey
val ok = Signature.getInstance("SHA256withRSA/PSS").run {
    initVerify(publicKey)
    update(plainBytes)
    verify(signature)
}

第四步:服务端二次验签(真实线上流程)

  1. 把证书链上报后台,后台通过证书里的公钥验签;
  2. 校验证书 OID:KeyUsage 必须包含 digitalSignature;
  3. 记录设备唯一标识(证书序列号+机型+ROM 版本),用于后续风控;
  4. 国内金融合规要求保存“签名原文、签名值、算法、时间戳”至少五年,后台需落库并防篡改(如追加 HMAC)。

异常处理:

  • 如果抛出 StrongBoxUnavailableException,降级到 TEE;
  • 如果抛出 UserNotAuthenticatedException,引导用户重新解锁;
  • 如果密钥永久失效(恢复出厂设置),走“重新激活”流程,让用户重新绑定并重新生成密钥对。

拓展思考

  1. 国密场景:AndroidKeystore 原生只支持 RSA/ECDSA,若对接国密 SM2/SM3,需要集成信安世纪、江南科友等第三方 so,并通过 JNI 调用,此时私钥只能放 so 自定义加密文件,无法享受硬件隔离,面试可答“我们采用双证书方案:RSA 做设备绑定,SM2 做业务签名,分别满足国际与监管两条线”。
  2. 密钥 attestation:国内银行 App 要求“设备证书”必须带 Google CA 或厂商根证书,部分华为老机型根证书不在银行白名单,需要后台预置厂商根证书链并定期更新;面试可提“我们维护一张机型→根证书映射表,通过 Firebase RemoteConfig 动态下发”。
  3. 性能与功耗:后台批量验签时,RSA 2048 纯 CPU 计算比 TEE 快 3 倍,但安全性下降;折中方案是“密钥分片”——高频批量验签用内存公钥,低敏操作用硬件私钥签名,面试可答“我们按业务敏感度做分级,支付类走 TEE,日志类走内存”。
  4. 隐私合规:密钥别名若包含用户手机号或 ID,会被国内应用市场认定为“个人信息存储”,需做匿名化映射;面试可补充“我们把别名做成 UUIDv4,和用户 ID 的映射表放服务端,本地无法逆向”。