如何使用 Android Keystore 实现 RSA 密钥对的生成与签名验证?
解读
面试官抛出这道题,核心想验证三件事:
- 是否真正动手用过 AndroidKeystore,而不是只背过“KeyStore 是硬件保险箱”这句话;
- 对国密合规、国内机型碎片化、TEE/SE 可用性差异有没有体感;
- 能否把“生成→使用→验证”闭环讲清楚,并主动说出防重放、防篡改、合规留痕等工程细节。
回答时切忌只贴官方模板代码,一定要结合国内场景:华为/小米/OPPO 对 StrongBox 支持度不同、部分低端机只有 TEE、国密算法需外挂库、应用市场审计要求密钥使用日志等。
知识点
- AndroidKeystore 是系统级密钥仓库,私钥不出安全硬件(TEE/SE),符合国内金融类 App“密钥不离设备”合规要求。
- 密钥必须在 KeyGenParameterSpec 里强制指定 PURPOSE_SIGN 和 PURPOSE_VERIFY,并显式设置 digests(SHA256/SHA384),否则在部分国内 ROM 上会因“算法缺失”崩溃。
- RSA 签名默认是 RSA PKCS#1 v1.5,国内政务类接口已强制迁移到 RSA-PSS;面试时要能说出如何切换 Signature 实例:“SHA256withRSA/PSS”。
- 签名结果通常 Base64 编码后随业务数据一起上传,服务端需记录签名值、签名算法、设备证书序列号,方便后续审计。
- 国内主流机型对硬件密钥有“密钥失效”场景:恢复出厂设置、刷机、解锁 Bootloader、用户关闭锁屏密码,都会导致密钥永久失效,App 需有“密钥失效重新注册”的补偿流程。
- 性能:TEE 下 RSA 2048 私钥签名一次约 30-40 ms,UI 线程直接调用会掉帧,必须放后台线程;面试官常追问“你怎么发现卡顿”,可答“使用 Systrace 看到 Binder IPC 耗时突刺”。
- 安全测评:国内第三方安全实验室(如中国电科、信通院)会检查是否开启 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)
}
第四步:服务端二次验签(真实线上流程)
- 把证书链上报后台,后台通过证书里的公钥验签;
- 校验证书 OID:KeyUsage 必须包含 digitalSignature;
- 记录设备唯一标识(证书序列号+机型+ROM 版本),用于后续风控;
- 国内金融合规要求保存“签名原文、签名值、算法、时间戳”至少五年,后台需落库并防篡改(如追加 HMAC)。
异常处理:
- 如果抛出 StrongBoxUnavailableException,降级到 TEE;
- 如果抛出 UserNotAuthenticatedException,引导用户重新解锁;
- 如果密钥永久失效(恢复出厂设置),走“重新激活”流程,让用户重新绑定并重新生成密钥对。
拓展思考
- 国密场景:AndroidKeystore 原生只支持 RSA/ECDSA,若对接国密 SM2/SM3,需要集成信安世纪、江南科友等第三方 so,并通过 JNI 调用,此时私钥只能放 so 自定义加密文件,无法享受硬件隔离,面试可答“我们采用双证书方案:RSA 做设备绑定,SM2 做业务签名,分别满足国际与监管两条线”。
- 密钥 attestation:国内银行 App 要求“设备证书”必须带 Google CA 或厂商根证书,部分华为老机型根证书不在银行白名单,需要后台预置厂商根证书链并定期更新;面试可提“我们维护一张机型→根证书映射表,通过 Firebase RemoteConfig 动态下发”。
- 性能与功耗:后台批量验签时,RSA 2048 纯 CPU 计算比 TEE 快 3 倍,但安全性下降;折中方案是“密钥分片”——高频批量验签用内存公钥,低敏操作用硬件私钥签名,面试可答“我们按业务敏感度做分级,支付类走 TEE,日志类走内存”。
- 隐私合规:密钥别名若包含用户手机号或 ID,会被国内应用市场认定为“个人信息存储”,需做匿名化映射;面试可补充“我们把别名做成 UUIDv4,和用户 ID 的映射表放服务端,本地无法逆向”。