“Unsupported key algorithm: ECDSA” when trying to put keys generated by Bouncy Castle into AndroidKeyStore

1.4k Views Asked by At

I'm trying to import various private keys into AndroidKeyStore. I'm using BouncyCastle to decode raw key data. While I am able to obtain usable KeyPairs, I can't put EC keys into AndroidKeyStore due to the following exception:

java.security.KeyStoreException: Unsupported key algorithm: ECDSA
    at android.security.keystore.AndroidKeyStoreSpi.getLegacyKeyProtectionParameter(AndroidKeyStoreSpi.java:348)
    at android.security.keystore.AndroidKeyStoreSpi.setPrivateKeyEntry(AndroidKeyStoreSpi.java:360)
    at android.security.keystore.AndroidKeyStoreSpi.engineSetKeyEntry(AndroidKeyStoreSpi.java:294)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1179)

which gets thrown here:

androidKeyStore.setKeyEntry(alias, 
        keyPair.getPrivate(),
        null,
        new Certificate[]{ generateCertificate(keyPair) });

(The certificate is generated as shown here except I added case "ECDSA": below case "EC":)

The problem seems to be the algorithm name, it's ECDSA instead of EC; when I use other means to obtain the key pair and its algorithm is EC, it works without issues.

What's going on here? Can I convert ECDSA keys to EC keys? Can I make BouncyCastle create EC keys in the first place?

For the record, this is how I create KeyPairs from byte array and passphrase: Edit: see fixed code in my answer below

1

There are 1 best solutions below

0
On

One trick is to not use Bouncy Castle for converting PCKS1 keys. On Android 10, the built-in BC provider does that part just fine. To get a key pair, call getKeyPair() on an object returned by simply JcaPEMKeyConverter() instead of JcaPEMKeyConverter().setProvider(bouncyCastleProvider).

full code:

private val bouncyCastleProvider = BouncyCastleProvider()
private val pkcs8DecryptorProvider = JceOpenSSLPKCS8DecryptorProviderBuilder()
        .setProvider(bouncyCastleProvider)
private val pemDecryptorProviderBuilder = JcePEMDecryptorProviderBuilder()
        .setProvider(bouncyCastleProvider)

private val pkcs8pemKeyConverter = JcaPEMKeyConverter().setProvider(bouncyCastleProvider)

// do NOT use BC for converting pkcs1 as for ECDSA it creates keys with algorithm "ECDSA"
// instead of "EC", and trying to put the former inside AndroidKeyStore results in
// exception “Unsupported key algorithm: ECDSA”
private val pkcs1pemKeyConverter = JcaPEMKeyConverter()


@Throws(Exception::class)   // this method throws just too many exceptions
fun makeKeyPair(keyReader: Reader, passphrase: CharArray): KeyPair {
    var obj = PEMParser(keyReader).readObject()

    // encrypted pkcs8 file, header: -----BEGIN ENCRYPTED PRIVATE KEY-----
    //   $ openssl genpkey -aes256 -pass pass:password -algorithm EC -out ecdsa.aes256.pkcs8 \
    //     -pkeyopt ec_paramgen_curve:P-384
    //   $ pkcs8 -topk8 -v2 des3 -passout pass:password -in rsa.pem -out rsa.des3.pkcs8
    if (obj is PKCS8EncryptedPrivateKeyInfo) {
        val decryptorProvider = pkcs8DecryptorProvider.build(passphrase)
        obj = /* PrivateKeyInfo */ obj.decryptPrivateKeyInfo(decryptorProvider)
    }

    // unencrypted pkcs8 file, header: -----BEGIN PRIVATE KEY-----
    //   $ openssl genpkey -algorithm RSA -out rsa.pkcs8 -pkeyopt rsa_keygen_bits:3072
    //   $ openssl genpkey -algorithm Ed25519 -out ed25519.pkcs8
    //   $ pkcs8 -topk8 -in rsa.pem -out rsa.pkcs8
    if (obj is PrivateKeyInfo) {
        val privateKey = pkcs8pemKeyConverter.getPrivateKey(obj)

        // Ed25519 can't be converted to PEM, so generate public key manually
        // the actual type here is probably BCEdDSAPrivateKey
        if (privateKey is EdDSAKey) {
            return KeyPair(genEd25519publicKey(privateKey, obj), privateKey)
        }

        // there is no direct way of extracting the public key from the private key.
        // to simplify things, we convert the key back to PEM and read it; this gets us a key pair
        obj = /* PEMKeyPair */ PEMParser(privateKey.toPem().toReader()).readObject()
    }

    // encrypted pkcs1 file, header like: -----BEGIN RSA PRIVATE KEY-----
    //                                    Proc-Type: 4,ENCRYPTED
    //                                    DEK-Info: AES-256-CBC,3B162E06B12794EA105855E7942D5A1A
    //   $ ssh-keygen -t ecdsa -m pem -N "password" -f ecdsa.aes128.pem
    //   $ openssl genrsa -aes256 -passout pass:password 4096 -out rsa.aes256.pem
    if (obj is PEMEncryptedKeyPair) {
        val decryptorProvider = pemDecryptorProviderBuilder.build(passphrase)
        obj = /* PEMKeyPair */ obj.decryptKeyPair(decryptorProvider)
    }

    // unencrypted pkcs1 file, header like: -----BEGIN RSA PRIVATE KEY-----
    //   $ ssh-keygen -t rsa -m pem -f rsa.pem
    //   $ openssl ecparam -name secp521r1 -genkey -noout -out ecdsa.pem
    if (obj is PEMKeyPair) {
        return pkcs1pemKeyConverter.getKeyPair(obj)
    }

    if (obj == null) throw IllegalArgumentException("File does not contain PEM objects")

    throw IllegalArgumentException("Don't know how to decode " + obj.javaClass.simpleName)
}


private fun PrivateKey.toPem(): String {
    val privateKeyPemObject = JcaMiscPEMGenerator(this, null).generate()
    val stringWriter = StringWriter()
    PemWriter(stringWriter).use { it.writeObject(privateKeyPemObject) }
    return stringWriter.toString()
}


private fun genEd25519publicKey(privateKey: EdDSAKey, keyInfo: PrivateKeyInfo): PublicKey {
    val privateKeyParameters = Ed25519PrivateKeyParameters(privateKey.encoded, 0)
    val publicKeyParameters = privateKeyParameters.generatePublicKey()
    val spi = SubjectPublicKeyInfo(keyInfo.privateKeyAlgorithm, publicKeyParameters.encoded)
    val factory = KeyFactory.getInstance(privateKey.algorithm, bouncyCastleProvider)
    return factory.generatePublic(X509EncodedKeySpec(spi.encoded))
}


fun ByteArray.toReader() = InputStreamReader(ByteArrayInputStream(this))
fun String.toReader() = StringReader(this)