How to Safely Implement Mobile Certificate Pinning and Prevent App Bricking
For years, certificate pinning has been the undisputed gold standard for securing mobile application traffic against Man-in-the-Middle (MitM) attacks. By hardcoding the expected certificate or public key directly within the app binary, developers ensure that the application will only communicate with a legitimate, trusted server, effectively neutralizing threats from compromised public Certificate Authorities (CAs) or malicious local network proxies.
However, the operational reality of certificate pinning has shifted dramatically. With Google pushing the CA/B Forum to reduce the maximum lifespan of public TLS certificates from 398 days to just 90 days, traditional static pinning is becoming an operational hazard. If a pinned certificate expires, is revoked, or requires emergency rotation, any user who hasn't updated their mobile app will experience a "bricked" application—a total loss of backend connectivity.
This tutorial covers how to implement modern mobile certificate pinning on both Android and iOS. We will focus on utilizing OS-level configurations, pinning public keys rather than certificates, establishing backup pins, and building a fail-safe operational lifecycle to ensure your security measures never accidentally take your application offline.
The Operational Risks of Legacy Static Pinning
Before diving into the implementation, it is critical to understand why legacy pinning strategies fail. Historically, developers would pin the exact leaf certificate or the Root CA certificate in their custom network code.
This approach creates severe fragility. Consider the historical expiration of the Let's Encrypt DST Root CA X3. When this root certificate expired, thousands of mobile applications that had pinned it—or relied on older trust stores—broke simultaneously. The developers had bound their application's availability to a cryptographic asset they did not control.
When an app is bricked due to a pinning failure, the recovery path is brutal. You must issue an emergency app update, wait for Apple and Google to review and approve the release, and then hope users actually download the update. During this window, your service is completely inaccessible.
To avoid this, modern pinning relies on three core principles:
1. Pin the SPKI, not the certificate: Pinning the Subject Public Key Info (SPKI) allows you to renew a certificate without changing the pin, provided you generate the new certificate using the same underlying private key.
2. The Rule of Three: Never deploy an app with only one pin.
3. OS-Level Enforcement: Avoid custom network interception code. Rely on Android's Network Security Configuration and Apple's App Transport Security (ATS).
Extracting the SPKI Hash
Both Android and iOS require the SHA-256 hash of the SPKI, encoded in Base64. You can extract this from your active server using OpenSSL.
Run the following command in your terminal, replacing api.yourcompany.com with your actual endpoint:
openssl s_client -servername api.yourcompany.com -connect api.yourcompany.com:443 < /dev/null 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
This will output a string that looks like this: 7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=. This is your primary pin.
Generating Backup Pins
You must also generate backup pins for keys that are not yet active on your server. Generate a new private key and extract its SPKI hash:
# Generate a new backup private key
openssl genrsa -out backup_key.pem 2048
# Extract the SPKI hash from the backup key
openssl rsa -in backup_key.pem -pubout -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Store backup_key.pem securely offline (e.g., in a hardware security module or a highly restricted secrets vault). If your primary key is ever compromised, you can use this backup key to generate a new certificate, and your mobile apps will already trust it.
Implementing Pinning on Android
Introduced in Android 7.0 (API level 24), the Network Security Configuration feature allows you to define your pinning strategy in a simple XML file, entirely removing the need for custom TrustManager implementations in Java or Kotlin.
Step 1: Create the Configuration File
Create a file named network_security_config.xml in your Android project under res/xml/.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<!-- Scope the pin strictly to your API domain -->
<domain includeSubdomains="true">api.yourcompany.com</domain>
<!-- Fail-open expiration date to prevent app bricking -->
<pin-set expiration="2025-12-31">
<!-- Primary Pin (Active Key) -->
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- Backup Pin 1 (Offline Key) -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
<!-- Backup Pin 2 (Intermediate CA) -->
<pin digest="SHA-256">r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=</pin>
</pin-set>
</domain-config>
</network-security-config>
Crucial detail: Notice the expiration attribute in the <pin-set> tag. This is your safety net. If a user does not update their app by this date, Android will disable pinning for this domain and fall back to the default system trust store. This means the app becomes slightly more vulnerable to MitM attacks on outdated devices, but it fails open rather than bricking entirely.
Step 2: Apply the Configuration
Link this XML file in your AndroidManifest.xml within the <application> tag:
<application
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
...>
<!-- App components -->
</application>
Implementing Pinning on iOS
Apple manages certificate pinning natively through App Transport Security (ATS). Configuration is handled within the application's Info.plist file.
Configuring the Info.plist
Open your Info.plist as source code and add the NSAppTransportSecurity dictionary. Apple allows you to pin either the leaf certificate (NSPinnedLeafIdentities) or the CA (NSPinnedCAIdentities). Pinning the Intermediate CA offers a great balance between security and operational flexibility.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>api.yourcompany.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedLeafIdentities</key>
<array>
<!-- Primary Pin -->
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</string>
</dict>
<!-- Backup Pin -->
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</string>
</dict>
</array>
</dict>
</dict>
</dict>
Unlike Android, iOS ATS does not currently support a native expiration date for pins in the Plist. This makes managing your certificate lifecycle and tracking expirations significantly more critical for iOS deployments.
Managing the Pinning Lifecycle and Expirations
The most common cause of pinning-related outages is a disconnect between the security team managing the mobile app and the infrastructure team managing the load balancers and TLS certificates. The infrastructure team might automate a certificate rotation using a new private key, completely unaware that the mobile app expects the old SPKI hash.
To prevent this, you must treat your pinned certificates and backup keys as critical infrastructure assets with strict monitoring.
This is exactly where Expiring.at becomes an essential part of your DevOps toolkit. Instead of relying on spreadsheets or disjointed calendar alerts to track when your pinned certificates (and just as importantly, the intermediate CAs you may have pinned) are up for renewal, you can use Expiring.at to monitor the specific endpoints your mobile apps communicate with. By setting up centralized tracking, your infrastructure and mobile teams have a single source of truth for certificate lifecycles, ensuring that any planned rotation of keys triggers an app update cycle well in advance.
When managing the lifecycle, adhere to this workflow:
1. Monitor the active key: Track the expiration of the currently deployed certificate.
2. Rotate early: When a certificate is 60 days from expiration, generate a new certificate using your backup key.
3. Deploy the infrastructure: Install the new certificate on your servers. Because the mobile app already has the backup key pinned, the transition will be seamless.
4. Update the app: Release a new version of the mobile app that promotes the backup key to the "primary" slot and introduces a brand new backup key for the next rotation cycle.
Advanced Considerations: Bypassing and Dynamic Pinning
While static pinning stops passive network interception and protects users on hostile networks (like public Wi-Fi), it is important to understand its limitations against active reverse engineering.
The Attacker's Perspective
Security professionals and attackers routinely bypass static pinning during