Certificate pinning with OkHttp

If your app is doing something relatively complicated, it will likely need a server to communicate with.

The basic step for ensuring secure communication with the server is well known: use HTTPS. What's lesser-known in secure app-server communication, is the certificate pinning. Not all apps will need this, but apps that transfer sensitive data from the app to the server might benefit from one additional security clause.

In short, certificate pinning ensures that your app will only connect to a server that has a specific certificate, not just a valid certificate. By default, when you connect to your server with HTTPS you require the server to have a valid certificate for the connected domain. This opens the window of man-in-the-middle attacks that some bad actor might pose to be your server to see the data you are sending. This is not the simplest thing to do (since the attacker will require to have a valid certificate as well) but is doable.

Do you need certificate pinning?

I don't think all apps need to implement certificate pinning. Only if your app is sending sensitive data to your server (e.g. credit card numbers) you should consider this. Why not everyone? Because it's adding some maintenance cost. Certificates expire and you will need to update the app with the new certificate that should be trusted when this happens. If you are fine with this maintenance cost, sure go ahead and implement it even if you are not the most "sensitive" app out there.

Ok, I am using OkHttp. How do I do it?

It's quite simple actually. Modify the OkHttp client builder as follow.

val client = OkHttpClient()
    .newBuilder().certificatePinner(
        certificatePinner = CertificatePinner.Builder()
            .add("example.com",
            "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
            .build()
        ).build()

Run your app and make a network call with this client. Search your logs for something similar to this.

javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
    Peer certificate chain: 
        sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
        sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
        sha256/DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

Replace the client code above to include the actual signatures of your certificate. You are done! Your app will only connect using HTTPS to your server and refuse to connect if it fails to detect your own certificate.

val client = OkHttpClient()
    .newBuilder().certificatePinner(
        certificatePinner = CertificatePinner.Builder()
            .add("example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
            .add("example.com", "sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")
            .add("example.com", "sha256/DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD")
            .build()
        ).build()

Might be a good idea to repeat this process for a backup domain. This way, if one of your domain certificates expires, you app can use the other one until you update the app.

If you want to pin all the subdomains of your domain, check the official doc.

Don't forget that this is not "unhackable". A bad actor might modify your app and change these hashes. But still, it requires significantly more effort to do this. There's nothing "unhackable" anyway, we just add as many protective measures as possible to keep bad actors away.

What if I don't use OkHttp?

It's slightly more complicated but here's the official doc (1, 2) on how to do this. There are also countless blog posts one search away.

Happy coding!