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

如何为Android KeyStore中导入的密钥对添加PURPOSE_AGREE_KEY属性

问题

在Android KeyStore中生成密钥对时,我们会设置KeyProperties(如KeyProperties.PURPOSE_SIGN)这类属性。我在外部生成了基于secp256r1曲线的明文密钥对,将其保存到KeyStore后,该密钥对可用于签名或验签,但无法进行ECDH密钥派生。推测这是缺少KeyProperties.PURPOSE_AGREE_KEY属性导致的,请问如何为KeyStore中的该密钥对添加此属性?

相关代码

1. 保存密钥对到KeyStore的代码

public static boolean saveKeyPair(PublicKey publicKey, PrivateKey privateKey, String alias) {
    KeyStore ks = null;
    try {
        ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null, null);

        byte[] cert = new CertificateManager.CertificateBuilder()
                    .setBasicConstraints(false)
                    .setkeyUsage(0)
                    .setPk(publicKey)
                    .addIssuer(BCStyle.CN, "KeyPair Save For Test")
                    .addSubject(BCStyle.CN, "KeyPair Save For Test")
                    .build(privateKey);

        X509Certificate[] chain = new X509Certificate[1];
        chain[0] = CertificateManager.createCert(cert);
        ks.setKeyEntry(alias, privateKey, null, chain);

        PublicKey pk = ks.getCertificate(alias).getPublicKey();
        if (ByteUtils.toHexString(pk.getEncoded()).equalsIgnoreCase(ByteUtils.toHexString(publicKey.getEncoded()))) {
            return true;
        }
    } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
        e.printStackTrace();
    }
    return false;
}

2. ECDH密钥派生代码

public static byte[] ecdh(String alias, PublicKey ePublicKey) {
    byte[] ret;
    try {
        KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null,null);
        KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)ks.getEntry(alias, null);
        PrivateKey privateKey = entry.getPrivateKey();
        KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH","AndroidKeyStore");
        keyAgreement.init(privateKey);
        keyAgreement.doPhase(ePublicKey, true);
        ret = keyAgreement.generateSecret();
        
        return ret;
    } catch (Exception e) {
           e.printStackTrace();
    }
    return new byte[0];
}

3. 外部生成密钥对代码

public static KeyPair genKeyPair(){
    try {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
        generator.initialize(spec);
        generator.initialize(256);
        return generator.generateKeyPair();
    } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
    throw new RuntimeException(e);
    }
}

运行错误日志

W/le.activitytes: Accessing hidden method Lcom/android/org/conscrypt/OpenSSLProvider;-><init>()V (unsupported,core-platform-api, reflection, allowed)
W/le.activitytes: Accessing hidden method Lcom/android/org/conscrypt/OpenSSLRandom;-><init>()V (unsupported, reflection, allowed)
W/System.err: java.security.InvalidKeyException: Keystore operation failed
W/System.err:     at android.security.keystore2.KeyStoreCryptoOperationUtils.getInvalidKeyException(KeyStoreCryptoOperationUtils.java:130)
W/System.err:     at android.security.keystore2.AndroidKeyStoreKeyAgreementSpi.ensureKeystoreOperationInitialized(AndroidKeyStoreKeyAgreementSpi.java:228)
W/System.err:     at android.security.keystore2.AndroidKeyStoreKeyAgreementSpi.engineInit(AndroidKeyStoreKeyAgreementSpi.java:96)
W/System.err:     at javax.crypto.KeyAgreement.init(KeyAgreement.java:498)
W/System.err:     at javax.crypto.KeyAgreement.init(KeyAgreement.java:470)
原因分析

Android KeyStore对导入密钥的用途限制,是通过KeyProtection对象配置的,而非仅依赖证书的KeyUsage字段。你之前调用ks.setKeyEntry时未指定KeyProtection,KeyStore会使用默认用途配置,其中不包含PURPOSE_AGREE_KEY,因此密钥无法用于ECDH密钥协商操作。

解决方案

改用KeyStore.setEntry方法保存密钥对,传入明确配置了所需用途(包含PURPOSE_AGREE_KEY,同时保留原签名用途)的KeyProtection对象。另外建议同步修改证书的KeyUsage,添加密钥协商标识,保持与KeyStore配置一致。

修改后的保存密钥对代码

import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import org.bouncycastle.asn1.x509.KeyUsage;

public static boolean saveKeyPair(PublicKey publicKey, PrivateKey privateKey, String alias) {
    KeyStore ks = null;
    try {
        ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null, null);

        // 1. 配置证书KeyUsage,添加密钥协商标识
        int keyUsage = KeyUsage.digitalSignature | KeyUsage.keyAgreement;
        byte[] cert = new CertificateManager.CertificateBuilder()
                    .setBasicConstraints(false)
                    .setkeyUsage(keyUsage) // 更新KeyUsage,支持签名和密钥协商
                    .setPk(publicKey)
                    .addIssuer(BCStyle.CN, "KeyPair Save For Test")
                    .addSubject(BCStyle.CN, "KeyPair Save For Test")
                    .build(privateKey);

        X509Certificate[] chain = new X509Certificate[1];
        chain[0] = CertificateManager.createCert(cert);

        // 2. 创建KeyProtection,配置允许的用途:签名 + 密钥协商
        KeyProtection keyProtection = new KeyProtection.Builder(
                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_AGREE_KEY)
                .setDigests(KeyProperties.DIGEST_SHA256) // 根据实际需求添加摘要算法
                .build();

        // 3. 使用setEntry替代setKeyEntry,传入KeyProtection
        ks.setEntry(alias, new KeyStore.PrivateKeyEntry(privateKey, chain), keyProtection);

        PublicKey pk = ks.getCertificate(alias).getPublicKey();
        return ByteUtils.toHexString(pk.getEncoded()).equalsIgnoreCase(ByteUtils.toHexString(publicKey.getEncoded()));
    } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
        e.printStackTrace();
    }
    return false;
}

注意事项

  1. KeyProtection.Builder的参数是用途位掩码,可同时指定多个用途(如PURPOSE_SIGN | PURPOSE_AGREE_KEY),确保密钥兼容原有签名功能和新增的密钥协商功能。
  2. 证书的KeyUsage需与KeyStore配置的用途匹配,虽然KeyStore主要以KeyProtection为准,但保持一致可避免潜在兼容性问题。
  3. 若自定义CertificateManager的setkeyUsage接收整数位值,直接传入0x80(对应keyAgreement的位掩码)也可实现相同效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 18:05:27