This article discusses the support for elliptic curves on Android. Its primary audience are engineers implementing or maintaining cryptographic protocols on Android. It hopefully also shows up as a helpful result when searching for error messages.

During a recent cryptography project on Android, I found that only few Elliptic Curves are supported by the built-in key pair generator. The typical error messages I got were along the line of java.security.InvalidAlgorithmParameterException: unknown curve name. Neither the official documentation nor any other online resource has a helpful list of the curves that I could use. Therefore, I decided to do a quick inventory and summarize the results here. In many places I link directly to the underlying source code. I hope this post helps next engineer to better understand exceptions like the following one:

Caused by: java.security.InvalidAlgorithmParameterException: unknown curve name: secp112r1
   at com.android.org.conscrypt.OpenSSLECKeyPairGenerator.initialize(OpenSSLECKeyPairGenerator.java:106)
   at java.security.KeyPairGenerator.initialize(KeyPairGenerator.java:192)
   ...

Android Security Crash-Course

Android is using the java.security framework where algorithms are implemented through providers. This framework is also what Java web services and desktop applications use. The available providers are usually registered by the system, but one can also load custom ones via Security#insertProviderAt.

Historically, Android has been using the BouncyCastle library which is a popular open-source provider with implementations for many algorithms. However, throughout the releases Android Marshmallow (23) to Android P (28) BouncyCastle has been mostly replaced by the AndroidOpenSSL provider. This one is specific to Android and also referred to as conscrypt. While the name indicates that OpenSSL is running under the hood, it is actually using the boringSSL fork that is maintained by Google. After my experiments, I figured out that the list of supported curve names for boringSSL are defined in ec.h#L104-L115:

// EC_GROUP_new_by_curve_name returns a fresh EC_GROUP object for the elliptic
// curve specified by |nid|, or NULL on unsupported NID or allocation failure.
//
// The supported NIDs are:
//   NID_secp224r1 (P-224),
//   NID_X9_62_prime256v1 (P-256),
//   NID_secp384r1 (P-384),
//   NID_secp521r1 (P-521)
//
// If in doubt, use |NID_X9_62_prime256v1|, or see the curve25519.h header for
// more modern primitives.
OPENSSL_EXPORT EC_GROUP *EC_GROUP_new_by_curve_name(int nid);

The binding OpenSSLECKeyPairGenerator explains how key sizes are mapped to curve names. This mapping has seen some changes over the years: 3fefbd8 in 2013 and ddcacec in 2015.

Results

For my experiments I wrote code that iterates over all available providers and 98 common elliptic curve identifiers. My code tries to generate a key pair for each combination.

val keyPair = KeyPairGenerator.getInstance("EC", provider).apply {
    initialize(ECGenParameterSpec(curveName))
    genKeyPair()
}

AndroidOpenSSL (built-in)

AndroidOpenSSL supports five curve names for key pair generation. While four of them are defined in the file mentioned above, secp256r1 might come as a surprise. However, secp256r1 is only a different name for the same curve described by prime256v1 as mentioned in RFC4992 Appendix A. However, I have not found the place where this alias is defined in the Android source code.

These are the results on a Pixel 3 phone with API level 30. They are identical on an emulator with API level 28 and a Moto G3 with API level 23.

== AndroidOpenSSL version 1.0 supports 5 curve names ==
prime256v1,
secp224r1, secp256r1, secp384r1, secp521r1

SpongyCastle (external dependency)

SpongyCastle is a specialized BouncyCastle distribution for Android that is easy to add as a dependency. It implements a large number of EC curves. I have tested the most-recent version 1.58.0.0 by adding the following to the build.gradle file:

implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.madgag.spongycastle:prov:1.58.0.0'

In addition one must register it as a provider before being able to access the new curves. This should ideally be done only once and just before calling the first cryptographic operation to minimise both memory and start-up impact:

import org.spongycastle.jce.provider.BouncyCastleProvider

Security.insertProviderAt(BouncyCastleProvider(), 1

SpongyCastle adds about ~1.8 MiB to a release-profile APK. It is a good choice when the application needs access to many of its features (e.g. to support many versions of your protocol).

== SC version 1.58 supports 87 curve names ==
B-163, B-233, B-283, B-409, B-571, K-163, K-233, K-283, K-409, K-571, P-192, P-224, P-256, P-384, P-521,
brainpoolp160r1, brainpoolp160t1, brainpoolp192r1, brainpoolp192t1, brainpoolp224r1, brainpoolp224t1, brainpoolp256r1, brainpoolp256t1, brainpoolp320r1, brainpoolp320t1, brainpoolp384r1, brainpoolp384t1, brainpoolp512r1, brainpoolp512t1,
c2pnb163v1, c2pnb163v2, c2pnb163v3, c2pnb176w1, c2pnb208w1, c2pnb272w1, c2pnb304w1, c2pnb368w1, c2tnb191v1, c2tnb191v2, c2tnb191v3, c2tnb239v1, c2tnb239v2, c2tnb239v3, c2tnb359v1, c2tnb431r1, 
prime192v1, prime192v2, prime192v3, prime239v1, prime239v2, prime239v3, prime256v1,
secp112r1, secp112r2, secp128r1, secp128r2, secp160k1, secp160r1, secp160r2, secp192k1, secp192r1, secp224k1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1, sect113r1, sect113r2, sect131r1, sect131r2, sect163k1, sect163r1, sect163r2, sect193r1, sect193r2, sect233k1, sect233r1, sect239k1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1,
sm2p256v1, wapip192v1

BouncyCastle (built-in, only old Androids)

BouncyCastle is still provided to applications with target API lower than Android P. The decision whether deprecation is enforced is implemented in the modified version of Providers.java. Note that KEYFACTORY.EC is part of the DEPRECATED_ALGORITHMS list. I tested the support provided by BouncyCastle on a Moto G3 with API level 23:

== BC version 1.52 supports 71 curve names ==
B-163, B-233, B-283, B-409, B-571, K-163, K-233, K-283, K-409, K-571, P-192, P-224, P-256, P-384, P-521,
c2pnb163v1, c2pnb163v2, c2pnb163v3, c2pnb176w1, c2pnb208w1, c2pnb272w1, c2pnb304w1, c2pnb368w1, c2tnb191v1, c2tnb191v2, c2tnb191v3, c2tnb239v1, c2tnb239v2, c2tnb239v3, c2tnb359v1, c2tnb431r1, 
prime192v1, prime192v2, prime192v3, prime239v1, prime239v2, prime239v3, prime256v1,
secp112r1, secp112r2, secp128r1, secp128r2, secp160k1, secp160r1, secp160r2, secp192k1, secp192r1, secp224k1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1, sect113r1, sect113r2, sect131r1, sect131r2, sect163k1, sect163r1, sect163r2, sect193r1, sect193r2, sect233k1, sect233r1, sect239k1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1

If you are trying to explicitly use BouncyCastle (i.e. providing it as the provider argument) where it is not available, you will receive an error message similar to the following:

Caused by: java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for KeyPairGenerator.EC.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
   at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:563)
   at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:330)
   ...

Other Alternatives (libsodium, Curve25519)

If you are developing protocols that run on multiple platforms, using a cross-platform library can save you from a lot of frustration. One of my personal favourites is libsodium which comes with support for Curve25519. It has plenty bindings to all important platforms and it comes great default choices. If added as a dependency you also can start worrying less about Android API changes and deprecations.

You might have noticed that Curve25519 is missing from the outputs above – it is certainly one of the candidates that I tried. It is my understanding that this is because this curve has not given a name by one of the large standards bodies. However, it is available as as part of CustomNamedCurves when using SpongyCastle:

import org.spongycastle.crypto.ec.CustomNamedCurves
import org.spongycastle.jcajce.provider.asymmetric.util.EC5Util

Security.insertProviderAt(BouncyCastleProvider(), 1)
val keyPair = KeyPairGenerator.getInstance("EC", "SC").apply {
    val spec = EC5Util.convertToSpec(CustomNamedCurves.getByName("Curve25519"))
    initialize(spec)
    genKeyPair()
}

The following curve names were not directly supported by any of the approaches that I tried:

Curves names not found: E-222, E-382, E-521, M-211, M-383, M-511, curve1174, curve25519, curve383187, ed448, x25519

Credits: cover photo by Paulius Dragunas on Unsplash