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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 15:10:41