Android SSL Socket Communication

2016-02-02

In the project, we need a SSL socket communication between Android and a Java server.
Here is some summaries,

  1. Basics of SSL protocol
    SSL 1.0, never publicly released
    SSL 2.0, 1995, deprecated in 2011
    SSL 3.0, 1996, deprecated in June 2015
    TLS 1.0, January, 1999, an upgrade of SSL 3.0
    TLS 1.1, April, 2006
    TLS 1.2, August 2008
    TLS 1.3, still draft in today, Feb 2,2016.

The handshake between Client and Server works as follow:

  1. Client sends message ClientHello, initialize connection request.
  2. Server sends message ServerHello, answer request.
  3. Server exchange ServerKey, sends the public key.
  4. Server HelloDone.
  5. Client exchange ClientKey, creates a session private key using Server’s public key, sends the private key to Server.
  6. Client change CipherSpec, informs Server to change the encryption algorithm.
  7. Client finish.
  8. Server change CipherSpec, confirms that the encryption algorithm changed.
  9. Server finish.

In the protocol above, only Client needs to verify the Server’s certificate, Server doesn’t need to verify the certificate of the Client, which is called One Way Handshake.

Next, we create the self-signed certificate, we use the keytool provided by Java, OpenSSL can also do this.

We have the following things to do:

  1. Create self-signed certificate on the server side.
  2. Create client side(Android) keystore (BKS)
  3. Export the certificate from the server.
  4. Import the certificate to the Android.

Note, One Way Handshake means only Client need to verify Server’s certificate.

  1. 1
    keytool -genkey -v -alias serverKey -keyalg RSA -keystore ./server.ks -storetype JKS -storepass server -keypass 1234
  2. 1
    keytool -genkey -alias androidKey -keyalg RSA -keystore ./client.bks -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.53.jar -storepass client -keypass 4321

    Note, Android KeyStore use the BouncyCastle(BKS) format, which is different from the Jave platform(JKS), that’s why we indicate the -providerclass and -providerpath, and google for the latest .jar.

  3. 1
    keytool -export -alias serverKey -keystore ./server_ks -file serverKey.cer -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.53.jar 

    Well, in case, we just indicate the -providerclass and -providerpath again. And the terminal may require the passcode of the keystore, use -storepass server in the first step.

  4. 1
    keytool -import -alias serverKey -file serverKey.cer -keystore ./client.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.53.jar

Till here, we have done the preparation, next we need to programm it on the Android side and server side respectively.
In Android, there are two keystores, the one in the Android itself, the other one with the self-signed certificate, which we need to load into our App.
I first create a KeyStore instance and initialize the keystore with the self-signed certificate, store.load(truststoreFile, "314durham".toCharArray());.
Next, I initialize the TrustManagerFactory using the KeyStore that sjust loaded.
Then, I create SSLContext and initialize it using the local KeyStore and the TrustManagerFactory. context.init(keyManager.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());.
From Oracle API document,

1
2
3
4
5
6
7
8
9
public final void init(KeyManager[] km,
TrustManager[] tm,
SecureRandom random)
throws KeyManagementException

Parameters:
km - the sources of authentication keys or null
tm - the sources of peer authentication trust decisions or null
random - the source of randomness for this generator or null

In Android 4.4 and 5.0, context.init(null, tmf.getTrustManagers(), new SecureRandom()); works, but in Android 6.0, the first parameter can’t be null, or there will be some SSL Handshake exceptions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static SSLSocket getSSLSocket(String ipAddr, int portNum, String keystorePath, boolean allowAllHosts) throws Exception {

//The KeyStore with the self-signed certificate.
InputStream truststoreFile = new FileInputStream(keystorePath);
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(truststoreFile, "4321".toCharArray());

//The KeyManagerFactory that supposed to load the Android local //keystore, but here we just use the same keystore that has the //self-signed certificate.
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("X509");
keyManager.init(store,"4321".toCharArray());

//The TrustManagerFactory loads the keystore with the //self-signed certificate.
TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
tmf.init(store);

//Create an instance of SSLContext.
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManager.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

//Create the Socket using SSLContext.
Socket socket = (SSLSocket)context.getSocketFactory().createSocket(ipAddr, portNum);

return (SSLSocket) socket;

}