How to implement okhttp SSL pinning for Android

2 minute read

Okhttp provides CertificatePinner you can see at: CertificatePinner

Generate public key file to SHA256

The certificate file should be full chain certificates (All certificates, including server certificate. The server certificate, is the first one in this file, followed by any intermediates.)

1openssl x509 -pubkey -noout -in [certificate file path] |
2openssl rsa -pubin -outform der 2>/dev/null |
3openssl dgst -sha256 -binary |
4openssl enc -base64

Or you can use the script sha256.sh to get public keys from domains. Save this as sha256.sh to your local.

 1#!/bin/bash
 2certs=`openssl s_client -servername $1 -host $1 -port 443 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p'`
 3rest=$certs
 4while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]
 5do
 6 cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
 7 rest=${rest#*-----END CERTIFICATE-----} 
 8 echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'` 
 9 echo "$cert" | openssl x509 -pubkey -noout | 
10     openssl rsa -pubin -outform der 2>/dev/null | 
11     openssl dgst -sha256 -binary | openssl enc -base64
12 echo -e "\n"
13done

make sha256.sh as executable file:

1chmod +x ./sha256.sh

Finally, execute the file sha256.sh + admokonugroho.com (domain) then, you will get the public keys.

1./sha256.sh admokonugroho.com

These are the results and take the seconds item because that is the server certificate.

/CN=*.admokonugroho.com
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=


/C=US/O=Let's Encrypt/CN=E1
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=


/C=US/O=Internet Security Research Group/CN=ISRG Root X2
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=


/C=US/O=Internet Security Research Group/CN=ISRG Root X1
C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=

Config public key sha256 for okhttp in Android

We use properties to provide the sensitive keys. I’m using local.properties on the project level.

Note: the config must be ignored on the commit. Make sure the format follows the example, using the prefix sha256\ + public key. For detail, you can see below.

1PRODUCTION_MAIN_TLS_PUBLIC_KEYS = {\"sha256/47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\"}
2PRODUCTION_BASE_URL = admokonugroho.com

Add code below to build.gradle

1buildConfigField 'String[]', 'TLS_PUBLIC_KEYS', localProps["PRODUCTION_MAIN_TLS_PUBLIC_KEYS"]
2builfConfigField 'String', 'BASE_URL', localProps['PRODUCTION_BASE_URL']

Finally, Including the code below to your okhttp client example:

 1private fun makeCertificatePinner(): CertificatePinner? {
 2    return BuildConfig.TLS_PUBLIC_KEYS.takeIf { it.isNotEmpty() }?.let { list ->
 3        val uri = URI(BuildConfig.BASE_URL) // this url will be applied ssl pinning
 4        CertificatePinner.Builder().apply {
 5            list.forEach {
 6                add(uri.host, it)
 7            } 
 8        }
 9    }.build()
10}
11
12private fun makeRestClient(
13    certificatePinner: CertificatePinner?,
14): OkHttpClient {
15    val builder = OkHttpClient.Builder()
16    certificatePinner?.let {
17        builder.certificatePinner(certificatePinner)
18    }
19    return builder.build()
20}

Tips SSL pinning on release application

SSL pinning applies to your production and stage environment. For a production environment, at least have 2 SSL with different expired dates. We don’t need to upgrade the app when renewing the SSL certificate In the production environment.