在 API Level <23 上通过 KeyStore 生成椭圆曲线 KeyPair

Generate elliptic curve KeyPair via KeyStore on API Level <23

我需要在 Android 中生成一个椭圆密钥对并将其存储到 KeyStore 以防止私钥被提取。

我能够使用 Spongycastle 库生成密钥对,但我无法在 KeyStore 中导入密钥对。首先,因为我没有证书,其次,即使我尝试创建一个,它也没有导入密钥。

虽然我想使用 KeyGenParameterSpec 生成密钥对,但在版本 23 以下的 API 中无法访问它。

总结一下我的问题,是否有一种非 hacky 的方法来使用一般的 Android 资源来解决这个问题?或者在 Lollipop 及更低版本上根本无法使用椭圆曲线键?

Android keystore system is a section on supported key generation algorithms的文档中。它指出:

Prior to API Level 23, EC keys can be generated using KeyPairGenerator of algorithm "RSA" initialized KeyPairGeneratorSpec whose key type is set to "EC" using setKeyType(String). EC curve name cannot be specified using this method -- a NIST P-curve is automatically chosen based on the requested key size.

如果您可以忍受这些限制,那么您可以使用 Android 密钥库将 API 级别低至 API 19。看起来您可以做到 API 18,但是设置密钥大小和密钥类型的必要方法直到 API 级别 19 才存在。class 的名称用于为 API 构建参数规范第 19 到 22 级是 KeyPairGeneratorSpec.Builder. This is very similar to the name of the class used for API level 23 and above, KeyGenParameterSpec.Builder,因此请注意不要混淆两者。

这里有一小段代码说明了上面的内容。它应该 运行 在 API 19.

private void createEcKey() throws Exception {
    Calendar start = Calendar.getInstance();
    Calendar end = Calendar.getInstance();
    end.add(Calendar.YEAR, 1);
    KeyPairGeneratorSpec spec =
            new KeyPairGeneratorSpec.Builder(this)
                    .setAlias("myKey")
                    .setKeySize(256)
                    .setKeyType("EC")
                    .setSubject(new X500Principal("CN=Dodgy Stuff"))
                    .setSerialNumber(BigInteger.valueOf(123456789L))
                    .setStartDate(start.getTime())
                    .setEndDate(end.getTime())
                    .build();
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(
            "RSA", "AndroidKeyStore");
    kpg.initialize(spec);
    KeyPair keyPair = kpg.generateKeyPair();
    ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
    ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate();

    //
    // The following will throw an Exception if uncommented, because
    //    the private key is not allowed to leave the protection of
    //    the Androud Keystore boundary.
    //
    // byte [] privEncoded = ecPrivateKey.getEncoded();
}

生成secp256r1密钥对代码:

val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val parameterSpec = KeyGenParameterSpec.Builder("container", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512)
                .build()
kpg.initialize(parameterSpec)
val keyPair = kpg.generateKeyPair()

val ecPublicKey = keyPair.public as ECPublicKey
val ecPrivateKey = keyPair.private as ECPrivateKey