SSL Pinning implementation for mobile apps

SSL Pinning (Certificate Pinning) is a security technique that makes a mobile app verify the server's SSL certificate against a pre-embedded value, rather than trusting any CA in the system. This effectively prevents Man-in-the-Middle (MITM) attacks.

How SSL Pinning Works

Normally, when an app connects to a server, the OS checks if the certificate was issued by a trusted CA. If an attacker has a fraudulent certificate from a legitimate CA, they can intercept traffic.

SSL Pinning solves this by having the app compare the server's certificate or public key against a hardcoded value. If they don't match, the connection is immediately rejected.

Types of SSL Pinning

1. Certificate Pinning

Embeds the full certificate (DER format) in the app. Requires app update when certificate is renewed. Best for apps where certificates rarely change.

2. Public Key Pinning (Recommended)

Embeds only the public key hash. You can renew the certificate without updating the app as long as you keep the same private key.

3. CA Pinning

Pins the CA's public key instead — more flexible but less secure since the CA could issue certificates to others.

Pros and Cons

iOS Implementation (Swift)

// URLSessionDelegate
func urlSession(_ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    
    guard let serverTrust = challenge.protectionSpace.serverTrust,
          let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    let pinnedCertData = // Load cert from app bundle
    let serverCertData = SecCertificateCopyData(certificate) as Data
    if serverCertData == pinnedCertData {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

Android Implementation (OkHttp)

val certificatePinner = CertificatePinner.Builder()
    .add("api.yourdomain.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

Best Practice: Use Public Key Pinning instead of Certificate Pinning to avoid app updates on renewal. Always pin at least 2 keys (1 primary + 1 backup) to prevent lockout.

How to Extract the Public Key Hash for Pinning

Before implementing SSL Pinning, you need to extract the public key hash from the certificate you intend to pin. The most reliable method uses OpenSSL, available on macOS and Linux. Run the following commands against your live API endpoint or a local certificate file:

# Extract hash directly from a live server
openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | base64

# Extract hash from a local .pem file
openssl x509 -in certificate.pem -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | base64

The resulting Base64 string is the pin value to embed in your app. Always extract and store hashes for both your current certificate and at least one backup certificate before you need it. This prevents emergency lockouts if you must rotate certificates unexpectedly.

Testing SSL Pinning with Proxy Tools

After implementing SSL Pinning, verifying that it works correctly under real conditions is critical. Security engineers and QA teams commonly use proxy tools to confirm that pinning blocks interception as expected. The table below summarizes common test scenarios and expected outcomes:

Scenario Expected Result Risk Level
Charles Proxy on a standard (non-rooted) device Connection rejected immediately Low
Frida on a rooted/jailbroken device May bypass if implementation is not hardened Medium–High
Network with a rogue Certificate Authority Connection rejected because hash does not match Very Low

Burp Suite Community Edition can also be used for the same interception tests. In all cases, a correctly pinned app should display a network error rather than a successful server response when the certificate does not match the embedded pin.

Certificate Rotation and Preventing User Lockout

The most common operational risk with SSL Pinning is certificate rotation. If a certificate expires or is replaced without updating the app's embedded pin, every user on the old app version loses connectivity instantly. Planning rotation carefully is as important as the implementation itself.

Key Strategies for Safe Rotation

Android Network Security Configuration for Pinning

Android 7.0 (Nougat) and later support a declarative pinning approach via an XML configuration file, which is less error-prone than writing custom TrustManager code. The system handles pin matching automatically, and misconfiguration is harder to introduce silently.

<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.yourdomain.com</domain>
    <pin-set expiration="2027-01-01">
      <!-- Primary pin -->
      <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
      <!-- Backup pin -->
      <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
    </pin-set>
  </domain-config>
</network-security-config>

Reference this file in your AndroidManifest.xml by adding android:networkSecurityConfig="@xml/network_security_config" to the application element. The expiration date acts as a safety valve: once that date passes, the OS stops enforcing pinning and falls back to standard CA validation, so users are not permanently locked out even if your team misses an update cycle.

Need SSL Certificate for Your App?

AsiaGB offers DV, OV, and Wildcard SSL certificates trusted by major CAs, perfect for mobile apps and APIs.

View SSL Certificates