Security

Android SSL certificate pinning to prevent Man-in-the-middle attack

Implementing SSL certificate pinning in mobile apps to secure the communication between the user's device and the backend

Wed 11 May 2016

When developing a mobile application, securing the communication between the user's device and the backend is an important step to guarantee the safety of the user's data.

TLS is the recommended protocol to encrypt communication between the server and the application. An incorrect implementation might allow hackers to intercept and modify network communication, which might include credentials, session cookies, etc.

In this topic, we will cover SSL certificate pinning implementation in the most used Android networking libraries (HttpsURLConnection, OkHTTP, Volley, Retrofit and Picasso).

Before getting our hands dirty with code, let me summarize why you should use SSL pinning to secure your application:

SSL pinning means including your server certificate directly in your mobile application and relying on the application's trust store instead of the device's trust store. This approach is secure against man-in-the-middle attacks when the device’s trust store is compromised. However, when you do SSL pinning, every time you change your SSL certificate, you have to update the app and push it to the mobile store.

Now let's check how we can implement Android certificate pinning in our mobile application:

Android certificate pinning using HttpsURLConnection

    CertificateFactory cf = CertificateFactory.getInstance("X.509");

    // Generate the certificate using the certificate file under res/raw/cert.cer
    InputStream caInput = new BufferedInputStream(getResources().openRawResource(R.raw.cert));
    Certificate ca = cf.generateCertificate(caInput);
    caInput.close();

    // Create a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", ca);

    // Create a TrustManager that trusts the CAs in our KeyStore
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);

    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, tmf.getTrustManagers(), null);

    // Tell the URLConnection to use a SocketFactory from our SSLContext
    URL url = new URL("https://example.com/faq/");
    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
    urlConnection.setSSLSocketFactory(context.getSocketFactory());
    InputStream in = urlConnection.getInputStream();
    theString = readInputStream(in);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");

Android certificate pinning using OkHTTP

    //okhttp version 3.x

    public CertificatePinning()
    {
        // We add the public key of our trusted server hashed and encoded base64
        client = new OkHttpClient.Builder().certificatePinner(new CertificatePinner.Builder()
        .add("https://example.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=").build()).build();
    }

    public void run() throws Exception
    {
        Request request = new Request.Builder().url("https://example.com/faq").build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    /**
    * response.handshake() contains the TLS handshake of the connection that carried this response, or null if the response
    * was received without TLS.
    */
        if(response.handshake())
        {
            for (Certificate certificate : response.handshake().peerCertificates())
            {
                //Pin returns SHA-256 hash of the public key
                String caPin = CertificatePinner.pin(certificate);

                // then you we to check the hash value and apply the corresponding action
            }
        }
    }

Android certificate pinning using Retrofit

    // Use the steps above to create OkHttpClient and set the custom client when building adapter
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://example.com")
    .addConverterFactory(GsonConverterFactory.create())
    .client(client)
    .build();
  Volley:

    CertificateFactory cf = CertificateFactory.getInstance("X.509");

    // Generate the certificate using the certificate file under res/raw/cert.cer
    InputStream caInput = new BufferedInputStream(getResources().openRawResource(R.raw.cert));
    Certificate ca = cf.generateCertificate(caInput);
    caInput.close();

    // Create a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore trusted = KeyStore.getInstance(keyStoreType);
    trusted.load(null, null);
    trusted.setCertificateEntry("ca", ca);

    // Create a TrustManager that trusts the CAs in our KeyStore
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(trusted);

    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, tmf.getTrustManagers(), null);

    SSLSocketFactory sf = context.getSocketFactory();
    mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(), new HurlStack(null, sf));

Android certificate pinning using Picasso

    CertificateFactory cf = CertificateFactory.getInstance("X.509");

    // Certificate file is under res/raw/cert.cer
    InputStream caInput = new BufferedInputStream(getResources().openRawResource(R.raw.cert));
    Certificate ca = cf.generateCertificate(caInput);
    caInput.close();

    // Create a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", ca);

    // Create a TrustManager that trusts the CAs in our KeyStore
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);

    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, tmf.getTrustManagers(), null);

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
    OkHttpDownloader okHttpDownloader = new OkHttpDownloader(okHttpClient);
    Picasso.Builder builder = new Picasso.Builder(context);
    builder.downloader(okHttpDownloader);
    Picasso sPicasso = builder.build();