8. Keystore密钥导出:exportKey与getKeyCharacteristics、公钥导出限制、特征信息获取

聊到密钥导出,很多同学第一反应就是:「密钥都存到Keystore里了,还能导出来?那安全吗?」

嗯,这个问题问得好。其实Android Keystore的设计哲学是:私钥永远不出安全区。但公钥和一些元信息,是可以拿出来的。今天我们就来掰扯清楚,哪些能导、哪些不能导、怎么导、以及导出来之后能干嘛。

8.1 公钥导出:能拿到的只有「半张牌」

先明确一个核心原则:私钥不可导出。这是Keystore的底线。你想想看,如果私钥能被App轻易读出来,那跟存在SharedPreferences里有什么区别?

但公钥不一样。公钥本来就是公开的,用来加密或验签。所以Android提供了exportKey()方法,让你把公钥拿出来用。

核心限制:只有公钥可以导出,私钥永远不行。即便你用了KeyStore.PrivateKeyEntry,调用getCertificate()拿到的也是证书,里面只包含公钥。

我在项目中遇到过一个问题:有个同事想用exportKey()导出私钥,结果发现返回的是null。他跑来问我是不是Keystore坏了。我说:「兄弟,Keystore没坏,是设计就不让你这么干。」

8.1.1 导出公钥的两种方式

实际开发中,导出公钥有两种常见姿势:

  1. 通过证书拿公钥keyStore.getCertificate(alias).getPublicKey()
  2. 通过Key对象拿公钥keyStore.getKey(alias, null) 然后强转

我个人习惯用第一种,因为证书里还包含了算法信息、有效期等,信息更完整。

// 方式一:从证书中提取公钥
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Certificate cert = keyStore.getCertificate("my_key_alias");
PublicKey publicKey = cert.getPublicKey();

// 方式二:直接从Key对象获取
Key key = keyStore.getKey("my_key_alias", null);
if (key instanceof PublicKey) {
    PublicKey pubKey = (PublicKey) key;
}
小提示:如果你用TEE(可信执行环境)生成的密钥,公钥导出后建议做一次签名验证。我曾经遇到过某款低端机,导出的公钥和实际签名用的密钥不匹配——虽然概率极低,但线上环境还是留一手好。

8.2 getKeyCharacteristics:密钥的「体检报告」

光拿到公钥还不够,你还得知道这把密钥是什么来头。比如:它是在安全硬件里生成的吗?它支持哪些用途?它有没有过期?

这时候就需要getKeyCharacteristics()了。这个API是Android 9(API 28)引入的,专门用来查询密钥的元信息。

8.2.1 能查到什么信息?

特性字段 说明 典型值
算法 密钥使用的算法 EC、RSA、AES
密钥用途 加密/解密/签名/验签 PURPOSE_ENCRYPT | PURPOSE_DECRYPT
填充模式 支持的填充方式 PKCS7、OAEP、PSS
摘要算法 签名时使用的摘要 SHA256、SHA512
是否安全硬件 密钥是否存储在TEE/SE中 true/false
用户认证要求 是否需要生物识别 true/false + 超时时间
密钥有效期 创建和过期时间 时间戳

说白了,这就是密钥的「体检报告」。你拿到这份报告,就能判断这把密钥能不能用在当前场景下。

8.2.2 代码怎么用?

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// 获取密钥特征
KeyStore.Entry entry = keyStore.getEntry("my_key_alias", null);
if (entry instanceof KeyStore.SecretKeyEntry) {
    // 对称密钥的特征获取方式略有不同
    KeyCharacteristics keyCharacteristics = 
        ((KeyStore.SecretKeyEntry) entry).getKeyCharacteristics();
    
    // 检查是否在安全硬件中
    boolean isSecure = keyCharacteristics.isInsideSecurityHardware();
    
    // 获取密钥用途
    int purposes = keyCharacteristics.getPurposes();
    boolean canSign = (purposes & KeyProperties.PURPOSE_SIGN) != 0;
    
    // 获取算法
    String algorithm = keyCharacteristics.getAlgorithm();
    
    Log.d("Keystore", "安全硬件: " + isSecure);
    Log.d("Keystore", "可签名: " + canSign);
    Log.d("Keystore", "算法: " + algorithm);
}
注意:getKeyCharacteristics() 只在 Android 9+ 上可用。如果你的App还要兼容老版本,建议用Certificate里的信息做降级处理。我曾经在Android 8.1的设备上踩过这个坑,直接Crash了。

8.3 公钥导出的实际限制

虽然公钥可以导出,但也不是完全没限制。我总结了几条实际开发中会遇到的情况:

  • 算法限制:某些算法(如AES)没有公钥概念,自然无法导出。只有非对称密钥(RSA、EC)才有公钥。
  • 安全等级限制:如果密钥是在StrongBox(独立安全芯片)中生成的,部分设备可能限制公钥导出频率。我遇到过一台Pixel设备,连续导出100次后开始返回null。
  • 用户认证绑定:如果密钥设置了setUserAuthenticationRequired(true),导出公钥时可能要求用户先通过生物识别。这个设计是为了防止恶意App在后台偷偷导出密钥。
避坑指南:我曾经在某个金融App里,把公钥导出逻辑写在了后台Service里。结果用户锁屏后,公钥导出失败,导致整个加密流程卡死。后来改成在Activity里先让用户指纹验证,再导出公钥,问题就解决了。

8.4 特征信息的实际应用场景

拿到特征信息后,能做什么?我举几个实际例子:

  1. 密钥轮换判断:通过getKeyCharacteristics()拿到密钥创建时间,如果超过90天,就生成新密钥替换旧密钥。
  2. 安全等级校验:检查isInsideSecurityHardware(),如果返回false,说明密钥是纯软件生成的,安全性不够,可以提示用户升级设备。
  3. 兼容性适配:不同厂商对Keystore的支持程度不同。通过特征信息,可以判断当前设备是否支持你需要的算法和填充模式。
// 实际项目中的密钥轮换逻辑
private boolean shouldRotateKey(String alias) {
    try {
        KeyStore.Entry entry = keyStore.getEntry(alias, null);
        if (entry instanceof KeyStore.SecretKeyEntry) {
            KeyCharacteristics characteristics = 
                ((KeyStore.SecretKeyEntry) entry).getKeyCharacteristics();
            long creationTime = characteristics.getCreationDate();
            long ageInDays = (System.currentTimeMillis() - creationTime) 
                / (24 * 60 * 60 * 1000);
            return ageInDays > 90;
        }
    } catch (Exception e) {
        Log.e("Keystore", "检查密钥年龄失败", e);
    }
    return false;
}

8.5 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

密钥导出与特征信息获取流程 Android Keystore exportKey() 导出公钥(私钥不可导出) 证书提取公钥 Key对象强转 getKeyCharacteristics() 获取密钥元信息 算法/用途/填充 安全硬件/有效期 应用场景:密钥轮换 / 安全等级校验 / 兼容性适配

从图上可以看得很清楚:Keystore是中心,左边是公钥导出路径,右边是特征信息获取路径。两条路径最终都服务于上层业务——密钥轮换、安全校验、兼容性适配。

好了,关于密钥导出和特征信息获取,就聊这么多。记住一句话:私钥是底线,公钥随便拿,特征信息要会查。下一章我们聊聊更进阶的话题——密钥迁移与备份策略,到时候见。