Using ECDH on Android

Elliptic curve cryptography (ECC) offers equivalent or higher levels of security than the currently widely deployed RSA and Diffie–Hellman (DH) algorithms using much shorter keys. For example, the computational effort  for cryptanalysis of a 160-bit ECC key is roughly equivalent to that of a 1024-bit key (NIST). The shift to ECC has however been fairly slow, mostly due to the added complexity, the need for standardization, and of course, patents. Standards are now available (more than a few, of course) and efficient implementations in both software and dedicated hardware have been developed. This,  along with the constant need for higher security, is pushing the wider adoption of ECC. Let's see if, and how we can use ECC on Android, specifically to perform key exchange using the ECDH (Elliptic Curve Diffie-Hellman) algorithm.

Android uses the Bouncy Castle Java libraries to implement some of its cryptographic functionality. It acts as the default JCE crypto provider, accessible through the java.security and related JCA API's. Bouncy Castle has supported EC for quite some time, and the most recent Android release, 4.0 (Ice Cream Sandwich, ICS), is based on the latest Bouncy Castle version (1.46), so this should be easy, right? Android, however, does not include the full Bouncy Castle library (some algorithms are omitted, presumably to save space), and the bundled version has some Android-specific modifications. Let's see what EC-related algorithms are supported on Android (output is from ICS, version 4.0.1):

BC/BouncyCastle Security Provider v1.46/1.460000
  KeyAgreement/ECDH
  KeyFactory/EC
  KeyPairGenerator/EC
  Signature/ECDSA
  Signature/NONEwithECDSA
  Signature/SHA256WITHECDSA
  Signature/SHA384WITHECDSA
  Signature/SHA512WITHECDSA

As seen above, it does support EC key generation, ECDH key exchange and ECDSA signatures. That is sufficient to generate EC keys and preform the exchange on the newest Android version, but as it turns out, currently more than 85% of devices are using 2.2 or 2.3. Android 4.0 doesn't even show up in the platform distribution graph. Let's check what is supported on a more mainstream version, such as 2.3 (Gingerbread). The output below is from stock 2.3.6:

BC/BouncyCastle Security Provider v1.45/1.450000

Which is exactly nothing: the JCE provider in Gingerbread is missing all EC-related mechanisms. The solution is, of course, to bundle the full Bouncy Castle library with our app, so that we have all algorithms available. It turns out that it is not that simple, though. Android preloads the framework libraries, including Bouncy Castle, and as a result, if you include the stock library in your project, it won't be properly loaded (you will most likely get a ClassCastException). This appears to have been fixed in 3.0 (Honeycomb) and later versions (they have changed the provider's package name), but not in our target platform (2.3). There are two main solutions to this:
  • use jarjar to rename the Bouncy Castle library package name we bundle
  • use the Spongy Castle library that already does this for us
We'll take the second option, because it's less work and the name sounds funny :) Using the library is pretty straightforward, but do check the Eclipse-specific instructions if you get stuck. Now that we have it set up, let's initialize the provider and see what algorithms it gives us. 

// add the provider
{
    Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}

SC/BouncyCastle Security Provider v1.46/1.460000
   AlgorithmParameters/SHA1WITHECDSA
   ...
   Cipher/BrokenECIES
   Cipher/ECIES
   KeyAgreement/ECDH
   KeyAgreement/ECDHC
   KeyAgreement/ECMQV
   KeyFactory/EC
   KeyFactory/ECDH
   KeyFactory/ECDHC
   KeyFactory/ECDSA
   KeyFactory/ECGOST3410
   KeyFactory/ECMQV
   KeyPairGenerator/EC
   KeyPairGenerator/ECDH
   KeyPairGenerator/ECDHC
   KeyPairGenerator/ECDSA
   KeyPairGenerator/ECGOST3410
   KeyPairGenerator/ECIES
   KeyPairGenerator/ECMQV
   Mac/DESEDECMAC
   Signature/ECDSA
   Signature/ECGOST3410
   Signature/NONEwithECDSA
   Signature/RIPEMD160WITHECDSA
   Signature/SHA1WITHCVC-ECDSA
   ...

This is much, much better. As you have probably noticed, the provider name has also been changed from 'BC' to 'SC' in order not to clash with the platform default. We will use 'SC' in our code, to ensure we are calling the correct crypto provider.

Now that we have a working configuration, let's move on to the actual implementation. JCE makes DH key exchange pretty straightforward: you just need to initialize the KeyAgreement class with the current party's (Alice!) private key, pass the other party's public key (who else but Bob), and call generateSecret() to get the shared secret bytes. To make things a little bit more interesting, we'll try to stimulate a (fairly) realistic example where we use pre-generated keys serialized in the PKCS#8 (for the private key) and X.509 (for the public) formats. We'll also show two ways of initializing the EC crypto system: by using a standard named EC curve, and by initializing the curve using discrete EC domain parameters.

To generate EC keys we need to first specify the required EC domain parameters:
  • an elliptic curve, defined by an elliptic field and the coefficients a and b, 
  • the generator (base point) G and its order n, 
  • and the cofactor h.
Assuming we have the parameters (we use the recommended values from SEC 2) in an instance of a class ECParams called ecp (see sample code) the required code looks like this:

ECFieldFp fp = new ECFieldFp(ecp.getP());
EllipticCurve ec = EllipticCurve(fp, ecp.getA(), ecp.getB());
ECParameterSpec esSpec = new ECParameterSpec(curve, ecp.getG(),
                ecp.getN(), ecp.h);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH", "SC");
kpg.initialize(esSpec);

Of course, since we are using standard curves, we can make this much shorter:

ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp224k1");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH", "SC");
kpg.initialize(ecParamSpec);

Next, we generate Alice's and Bob's key pairs, and save them as Base64 encoded strings in the app's shared preferences (we show only Alice's part, Bob's is identical):

KeyPair kpA = kpg.generateKeyPair();

String pubStr = Crypto.base64Encode(kpA.getPublic().getEncoded());
String privStr = Crypto.base64Encode(kpA.getPrivate().getEncoded());

SharedPreferences.Editor prefsEditor = PreferenceManager
                .getDefaultSharedPreferences(this).edit();

prefsEditor.putString("kpA_public", pubStr);
prefsEditor.putString("kpA_private", privStr);
prefsEditor.commit();

If we save the keys as files on external storage as well, it's easy to check the key format using OpenSSL:

$ openssl asn1parse -inform DER -in kpA_public.der
cons: SEQUENCE          
cons: SEQUENCE          
prim: OBJECT            :id-ecPublicKey
cons: SEQUENCE          
prim: INTEGER           :01
cons: SEQUENCE          
prim: OBJECT            :prime-field
prim: INTEGER           :FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73
cons: SEQUENCE          
prim: OCTET STRING      [HEX DUMP]:0000000000000000000000000000000000000000
prim: OCTET STRING      [HEX DUMP]:0000000000000000000000000000000000000007
prim: OCTET STRING      [HEX DUMP]:043B4C382CE37AA192A4019E763036F4F5DD4...
prim: INTEGER           :0100000000000000000001B8FA16DFAB9ACA16B6B3
prim: INTEGER           :01
prim: BIT STRING         

We see that it contains the EC domain parameters (G is in uncompressed form) and the public key itself as a bit string. The private key file contains the public key plus the private key as an octet string (not shown).

Now that we have the two sets of keys, let's perform the actual key exchange. First we read the keys from storage, and use a KeyFactory to decode them (only Alice's part is shown):

SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(this);
String pubKeyStr = prefs.getString("kpA_public", null);
String privKeyStr = prefs.getString("kpB_private", null);

KeyFactory kf = KeyFactory.getInstance("ECDH", "SC");

X509EncodedKeySpec x509ks = new X509EncodedKeySpec(
                Base64.decode(pubKeyStr));
PublicKey pubKeyA = kf.generatePublic(x509ks);

PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec(
                Base64.decode(privKeyStr));
PrivateKey privKeyA = kf.generatePrivate(p8ks);

After all that work, the actual key exchange is pretty easy (again, only Alice's part):

KeyAgreement aKA = KeyAgreement.getInstance("ECDH", "SC");
aKeyAgreement.init(privKeyA);
aKeyAgreement.doPhase(pubKeyB, true);

byte[] sharedKeyA = aKA.generateSecret();

Finally, the all important screenshot:


As you can see, Alice's and Bob's shared keys are the same, so we can conclude the key agreement is successful. Of course, for a practically useful cryptographic protocol that is only part of the story: they would need to generate a session key based on the shared secret and use it to encrypt communications. It's not too hard to come up with one, but inventing a secure protocol is not a trivial task, so the usual advice applies: use TLS or another standard protocol that already supports ECC.

To sum things up: you can easily implement ECDH using the standard JCE interfaces available in Android. However, older version (2.x) don't include the necessary ECC implementation classes in the default JCE provider (based on Bouncy Castle). To add support for ECC, you need to bundle a JCE provider that does and is usable on Android (i.e., doesn't depend on JDK classes not available in Android and doesn't clash with the default provider), such as Spongy Castle. Of course, another way is to use a lightweight API not based on JCE. For this particular scenario, Bouncy/Spongy Castle provides ECDHBasicAgreement.

That concludes our discussion of ECDH on Android. As usual, the full source code of the example app is available on Github for your hacking pleasure.

Comments

allan atukunda said…
Tried running the source code but it can't run. This is the message I get: "Your project contains errors, please fix them before running your application"..
Nikolay Elenkov said…
You might want to check what the actual errors are and fix them. Most probably a missing library or a project configuration problem. Check the README on GitHub for details.
allan atukunda said…
I managed to run the source code (after setting up the Spongy Castle library), but I get a Force close message when the app loads on my emulator: "The application ECDH-KX (process.org.nick.ecdhkx) has stopped unexpectedly. Please try again."

I just can't figure out the problem from my LogCat..
allan atukunda said…
This is a message I have picked from my LogCat that seems to be the source of the problem:

12-08 02:58:33.843: E/dalvikvm(312): Could not find class 'org.spongycastle.jce.provider.BouncyCastleProvider', referenced from method org.nick.ecdhkx.Crypto.

Any help ??
Nikolay Elenkov said…
It seems the Spongy Castle library is not properly linked into your app. If you are using a recent ADT version, you have to copy the library in the libs/ directory of the project and rebuild.
tran dong said…
Which function for encryption with these keys?
Xinran Wei said…
how can I overwrite the signature algrithem? exactly the mothed: update(byte [] data)?
Nikolay Elenkov said…
This is a key exchange sample, not sure what you mean?
Adnan Kuleta said…
I have a Problem concerning to different types of curves. Using the code it only acepts for 2 types of curves. Trying to play with other e.g. secp160r1 or sect163k1 than throws immediatelly an erro and that from AsyncTask. Does anymone had allready such a problem..?? Please i need a help
Shweta Angadi said…
I have included the spongy lib but still getting the error,...... log cat mesage is

E/dalvikvm(755): Could not find class 'org.spongycastle.jce.provider.BouncyCastleProvider', referenced from method org.nick.ecdhkx.Crypto.
Kindly help to resolve
Shweta Angadi said…
Ohhhhh sorry to disturb.... issue got resolved.... reason is i created lib folder in android project and placed jar, but it should be "libs"..... thank u
Matt Rothmeyer said…
I know this question is a while after your post, but I was wondering if anyone can help me out. I have gotten the application up and running but I am not quite sure how to encrypt with the keys and was wondering if you could point me in the right direction? Can I just use Cipher.getInstance("ECIES", "SC") ? to generate my cypher instance?
Nikolay Elenkov said…
I don't think Bouncy Castle supports ECIES directly (do check). You need to implement the steps yourself: derive keys from the shared secret using a KDF, encrypt using AES, etc, apply MAC to encrypted data.
diggyone said…
Thank you for this example, I got it running fine. I noticed that when I use a standard curve (secp128r1), it doesn't seem to matter if I store or regenerate the public/private keys (for Alice or Bob), I still get matching shared secrets. Why is it recommended to use a standard curve? It seems to me that an attacker could just guess which curve is being used, generate a public/private key and obtain the shared secret. Am I missing an important step?
Nikolay Elenkov said…
Yes, you are :) An attacker typically doesn't have access to Alice or Bob's private key. He can observer their exchange and intercept the public keys, but that is not sufficient to generate the shared secret.
diggyone said…
Thank you, I get it. It protects against a man in the middle attack. I was thinking about a device that spoofs Alice would be able to connect to Bob if it knew which named curve was being used, but that's a separate problem.
Nikolay Elenkov said…
It doesn't actually protect from a man-in-the-middle attack. You can only protect against MiTM if you are sure that the public key you are getting actually belongs to Bob (or Alice, respectively). You will need to exchange key fingerprints out of band for this, or use certificates. As for named curves, they are recommended because they are considered secure (although there is currently some debate about that) and provide easier interoperability (you don't have to exchange all curve parameters, etc).
Hi, I'm currently working on an ECC project. So the project goes like this, I want to send an encrypted message from my android phone using NFC to a contactless smart card. The tag is being developed by a different team, so that's not the problem here.

I've already made a program on eclipse to send and receive the message, but I want the message to be encrypted.

The problem is, I'm quite new to android eclipse but I've already read the descriptions on android developers and all of the problems related to ECC on stackoverflow including your blog, but I still can't find a way to send and receive an ECC encrypted message using NFC. Can you please help me?
Nikolay Elenkov said…
It seems like you need to do a lot more reading. In order to encrypt, you need to a key. And in order for the contactless card to be able to decrypt your message, they need either the same key, or its public counterpart if it is the private key in a key pair. So first, agree on a protocol with the card people, figure out what the key is or how to derive it, then encrypt and encode in the agreed protocol. Finally send to card and receive response.
Hi, thanks for your fast reply. I've done what you suggested me (agreeing with the card developer team) and we've agreed on a protocol to use. Well, the problem is I don't know the code on android to encrypt the message.
Currently I'm trying to combine the codes from my project with the code you posted here. If it helps, can I send you my project so you can take a quick look at it? Or do you have any reading materials for me to learn?
Thanks again for your help
Donald Hoskins said…
Is it possible to remove/replace BouncyCastle with SpongyCastle to be the default provider in the AOSP codebase itself? From what I've read, BC was used to save space, but space isn't so much of an issue with the devices capable of supporting 4.4.x. While I understand individual APK developers can include SC, is there a practical way to implement SC system-wide?
Nikolay Elenkov said…
If you are building your own ROM you can do whatever you want. However, BC hasn't been the default provider for years, and there is really no need to include SC, you can just adjust the patches to BC. If you wan't to expose the full BC API (so called 'lightweight API'), you can do that too, but you'll be getting a bit far off from the official API.
Ngân Đỗ said…
I don't understand secp160k1, secp224k1. Please tell me about this?
LordGroove said…
secp160k1 is a Elliptic Curve (Koblitz Curve) defined over a finite field at 160 bit. secp224k1 is a Elliptic Curve (Koblitz Curve) defined over a finite field at 224 bit. If you want, you can read this document (SEC 2: Recommended Elliptic Curve Domain Parameters - http://perso.univ-rennes1.fr/sylvain.duquesne/master/standards/sec2_final.pdf) to understand elliptic curve domain parameters.
Ngân Đỗ said…
Thanks for your reply
Ngân Đỗ said…
Thanks for your reply. Can you please tell me about information:Based on this program I want to develop more modular encoding and decoding, but i don't understand encoding and decoding?
kashyap said…
1. How to perform Encryption using ECDH ?
2. As it is assymetric what will be public key and what will be private key?
Soha Emad said…
will this code work on android studio?
Unknown said…
Hi

This post is very informative. How do we use Bouncy Castle now on the newer versions of Android? I tried using import org.bouncycastle.jce.provider; but bouncycastle cannot be found. What am I missing?

Popular posts from this blog

Password storage in Android M

Decrypting Android M adopted storage

Unpacking Android backups