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
- Pro: Prevents MITM attacks even if the attacker has a valid CA certificate
- Pro: Blocks proxy tools like Burp Suite and Charles from intercepting traffic
- Con: Certificate Pinning requires an app update every time the certificate is renewed
- Con: Makes debugging and pentesting more difficult
- Con: Can be bypassed by attackers with root access to the device
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
- Pre-embed backup pins: Add the public key hash of the next certificate to the app before renewing the current one, giving yourself a rolling window of safety.
- Set renewal reminders at 60 days: Certificate expiry is predictable — automate alerts so your team has time to release an app update before the old pin becomes invalid.
- Use the Android expiration attribute: The
expirationfield in Android Network Security Config disables strict pinning after the specified date as a safety fallback, preventing permanent lockout. - Dynamic pinning (advanced): Some teams fetch pin values from a signed API endpoint at runtime, allowing pin updates without a full app release. This requires careful signing and signature verification to avoid creating a new attack surface.
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