如何为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; }
注意事项
KeyProtection.Builder的参数是用途位掩码,可同时指定多个用途(如PURPOSE_SIGN | PURPOSE_AGREE_KEY),确保密钥兼容原有签名功能和新增的密钥协商功能。- 证书的
KeyUsage需与KeyStore配置的用途匹配,虽然KeyStore主要以KeyProtection为准,但保持一致可避免潜在兼容性问题。 - 若自定义
CertificateManager的setkeyUsage接收整数位值,直接传入0x80(对应keyAgreement的位掩码)也可实现相同效果。
内容的提问来源于stack exchange,提问作者dante0610
相关产品推荐
相关产品推荐

