Android 12中ECDH密钥协商生成的密钥对始终无效问题
问题描述
按照Android官方指南编写了ECDH密钥协商代码,代码如下:
private static final String EC_SPEC = "p-256"; private static final String KEY_PAIR_NAME = "abcdefgh"; private static final String MAC_ALG = "HMACSHA256"; private static final String INFO_TAG = "ECDH p-256 AES-256-GCM-SIV\0"; private static final String KEY_AGREEMENT_ALG = "ECDH"; private static final String KEY_STORE_PROVIDER = "AndroidKeyStore"; public static KeyPair generateKeys() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, ParseException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, KEY_STORE_PROVIDER); keyPairGenerator.initialize( new KeyGenParameterSpec.Builder( KEY_PAIR_NAME, KeyProperties.PURPOSE_AGREE_KEY) .setAlgorithmParameterSpec(new ECGenParameterSpec(EC_SPEC)) .setUserAuthenticationRequired(false) .setKeyValidityEnd(DateFormat.getDateInstance().parse("Aug 1, 2199")) .build()); return keyPairGenerator.generateKeyPair(); } public static byte[] sharedSecret(PrivateKey mine, PublicKey remote) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { KeyAgreement keyAgreement = KeyAgreement.getInstance(KEY_AGREEMENT_ALG, KEY_STORE_PROVIDER); //Line 55 here ↓ where error occurs keyAgreement.init(mine); keyAgreement.doPhase(remote, true); return keyAgreement.generateSecret(); }
执行代码时抛出以下错误:
W/System.err: android.security.keystore.KeyPermanentlyInvalidatedException: Key permanently invalidated W/System.err: at android.security.keystore2.KeyStoreCryptoOperationUtils.getInvalidKeyException(KeyStoreCryptoOperationUtils.java:123) 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) W/System.err: at app.exploitr.sec.rts.security.ECCUtil.sharedSecret(ECCUtil.java:55)
测试代码如下:
try { KeyPair one = ECCUtil.generateKeys(); KeyPair two = ECCUtil.generateKeys(); byte[] sec_one = ECCUtil.sharedSecret(one.getPrivate(), two.getPublic()); byte[] sec_two = ECCUtil.sharedSecret(two.getPrivate(), one.getPublic()); Log.d("TAG 1st SHARED", new String(sec_one)); Log.d("TAG 2nd SHARED", new String(sec_two)); } catch (IOException | GeneralSecurityException | ParseException e) { e.printStackTrace(); }
尽管已禁用用户认证并设置了长有效期,密钥仍立即永久失效。仅拥有一台Android 12设备(KeyProperties.PURPOSE_AGREE_KEY自API 31引入),在本地生成并测试双方密钥,寻求解决方法。
解决方案
- 核心修复:使用唯一密钥别名
问题根源是两次调用generateKeys()时复用了同一个固定别名abcdefgh。在AndroidKeyStore中,当用已存在的别名生成新密钥对时,旧密钥会被系统标记为永久失效。修改generateKeys()方法,支持传入动态生成的唯一别名:
// 修改后的generateKeys方法,接收自定义别名参数 public static KeyPair generateKeys(String keyAlias) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, ParseException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, KEY_STORE_PROVIDER); keyPairGenerator.initialize( new KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_AGREE_KEY) .setAlgorithmParameterSpec(new ECGenParameterSpec(EC_SPEC)) .setUserAuthenticationRequired(false) .setKeyValidityEnd(DateFormat.getDateInstance().parse("Aug 1, 2199")) .build()); return keyPairGenerator.generateKeyPair(); }
测试时传入不同别名:
try { KeyPair one = ECCUtil.generateKeys("ec_key_pair_1"); KeyPair two = ECCUtil.generateKeys("ec_key_pair_2"); byte[] sec_one = ECCUtil.sharedSecret(one.getPrivate(), two.getPublic()); byte[] sec_two = ECCUtil.sharedSecret(two.getPrivate(), one.getPublic()); // 用Base64编码二进制密钥后再打印,避免乱码 Log.d("TAG 1st SHARED", Base64.encodeToString(sec_one, Base64.DEFAULT)); Log.d("TAG 2nd SHARED", Base64.encodeToString(sec_two, Base64.DEFAULT)); } catch (IOException | GeneralSecurityException | ParseException e) { e.printStackTrace(); }
- 额外优化点
- 共享密钥是二进制数据,直接转字符串会出现乱码,建议用Base64编码后再输出日志,方便验证双方生成的共享密钥是否一致。
- 若需要复用别名,可提前通过KeyStore检查别名是否存在,避免覆盖旧密钥导致失效:
public static boolean isKeyAliasExists(String alias) throws KeyStoreException, NoSuchProviderException, IOException, CertificateException { KeyStore keyStore = KeyStore.getInstance(KEY_STORE_PROVIDER); keyStore.load(null); return keyStore.containsAlias(alias); }
内容的提问来源于stack exchange,提问作者exploitr
相关产品推荐
相关产品推荐

