Implementing Dynamic Certificate Pinning Without Bricking Your Mobile App

Certificate pinning has historically been the gold standard for preventing Man-in-the-Middle (MitM) attacks in mobile applications. By hardcoding the expected server certificate or public key directly...

Tim Henrich
July 21, 2026
6 min read
32 views

Implementing Dynamic Certificate Pinning Without Bricking Your Mobile App

Certificate pinning has historically been the gold standard for preventing Man-in-the-Middle (MitM) attacks in mobile applications. By hardcoding the expected server certificate or public key directly into the app, developers ensured that even if a device's trust store was compromised, the application would only communicate with the legitimate server.

But the landscape of mobile security and certificate management has shifted dramatically. Today, Apple and Google actively discourage static certificate pinning. The reason is simple: static pinning causes significantly more outages than it prevents attacks. When a certificate expires, or a Certificate Authority (CA) rotates an intermediate certificate, an app with a hardcoded, outdated pin will reject the new, perfectly valid certificate. The result is a "bricked" app that cannot communicate with its backend API until the user manually downloads an update from the App Store or Google Play.

Modern mobile security requires a transition to Dynamic Certificate Pinning, relying on OS-level network security configurations, and integrating certificate lifecycle management directly into the DevOps pipeline.

The Mechanics of the "Bricked App" Scenario

To understand why static pinning is dangerous, we have to look at the certificate rotation lifecycle.

Let's say your mobile app pins the exact leaf certificate of your API server. That certificate has a lifespan—often 398 days, or just 90 days if you use Let's Encrypt. When your DevOps team automatically renews that certificate via an ACME client, the server begins presenting the new certificate. The mobile app, however, is still looking for the cryptographic hash of the old certificate. The TLS handshake fails, the connection drops, and the user is locked out.

This isn't just a theoretical problem. When the Let's Encrypt DST Root CA X3 expired in late 2021, thousands of mobile apps and IoT devices that had incorrectly pinned the root certificate—or failed to implement a backup trust store—broke simultaneously.

To prevent this, organizations must implement pinning strategies that account for expiration, rotation, and CA compromises without requiring synchronous app updates.

Extracting the Right Hash: Pinning the SPKI

The most critical best practice in modern certificate pinning is to pin the Subject Public Key Info (SPKI) rather than the entire certificate.

When you pin the entire certificate, any change—including a routine renewal—changes the hash and breaks the pin. When you pin the SPKI, you are pinning the underlying public key. This allows your infrastructure team to renew the certificate using the exact same Certificate Signing Request (CSR) and private key. The new certificate will have a new expiration date and signature, but the public key remains identical, meaning the existing mobile app pin will still validate.

You can extract the SPKI SHA-256 hash from a certificate using standard OpenSSL commands:

openssl x509 -in api_certificate.crt -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64

This command outputs a Base64-encoded SHA-256 hash (e.g., 7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=) which you will inject into your mobile application's configuration.

Modern Implementation on Android

Android 14 and 15 have made it significantly harder for users to install custom root certificates that affect app traffic, reducing the baseline need for custom pinning code in non-critical apps. For apps that still require it (such as banking or healthcare), the standard approach is the Network Security Configuration (NSC) XML file.

Instead of writing custom TrustManager code, you define your pins declaratively. This approach is highly recommended because it supports an expiration attribute. If the date passes, the app fails open to the system's default trust store, preventing a total lockout if you forget to update the app with new pins.

Create or update 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>
        <!-- Fail open after Dec 31, 2025 to prevent bricking -->
        <pin-set expiration="2025-12-31">
            <!-- Primary Pin (Current Intermediate CA SPKI) -->
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
            <!-- Backup Pin (Future Intermediate CA or Offline Root SPKI) -->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

Reference this file in your AndroidManifest.xml:

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

Modern Implementation on iOS

Apple continues to push developers toward App Transport Security (ATS) and Certificate Transparency. Starting in iOS 14, Apple introduced NSPinnedDomains in the Info.plist, which replaces the need to write complex, error-prone URLSessionDelegate code for SSL verification.

You define the domain names and their corresponding SPKI SHA-256 hashes directly in your property list.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSPinnedDomains</key>
    <dict>
        <key>api.yourdomain.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSPinnedLeafIdentities</key>
            <array>
                <dict>
                    <key>SPKI-SHA256-BASE64</key>
                    <string>7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</string>
                </dict>
                <dict>
                    <key>SPKI-SHA256-BASE64</key>
                    <string>fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</string>
                </dict>
            </array>
        </dict>
    </dict>
</dict>

For older iOS versions, or if you need robust telemetry to report pinning failures back to your security team, open-source libraries like TrustKit remain the industry standard.

Cross-Platform Considerations: React Native and Flutter

If you are building cross-platform applications, relying on the native OS configurations (NSC and ATS) is still the safest route.

  • React Native: The default fetch API does not natively support custom pinning logic. If you need dynamic pinning beyond the OS-level XML/Plist files, you will typically rely on native modules like react-native-ssl-pinning.
  • Flutter: Flutter bypasses the native OS network stack and uses its own BoringSSL implementation. This means Android's NSC and iOS's ATS will not automatically apply to standard Dart HttpClient requests. You must use the SecurityContext class to set trusted certificates, or rely on community packages like http_certificate_pinning to validate SPKI hashes manually.

Strategic Best Practices for Pinning

Writing the code is the easy part. Managing the cryptographic lifecycle is where most organizations fail. To implement pinning safely, adhere to these architectural rules.

1. Pin the Intermediate CA, Not the Leaf or Root

Pinning the leaf (end-entity) certificate requires an app update every time the certificate is renewed. If you use automated 90-day certificates, this is an administrative nightmare.

Pinning the Root CA is too broad. If the Root CA is compromised, your app remains vulnerable. Furthermore, CAs occasionally cross-sign or retire roots, which can break your app unexpectedly.

The optimal balance is pinning the Intermediate CA. Intermediates rotate much less frequently than leaf certificates (often lasting several years) but offer a smaller blast radius than Root CAs.

2. Always Include Backup Pins

Never deploy an app with only one pin. You must include at least one backup pin for a certificate or key pair that you physically control but is not currently in use. If your active certificate is compromised or your CA suddenly revokes it, your DevOps team can immediately switch the server to the backup certificate. Because the backup pin is already in the mobile app, traffic continues seamlessly.

3. Integrate with DevOps and Certificate Tracking

Pinning is no longer just a mobile developer's responsibility; it is a cross-functional DevOps requirement. When a new certificate is provisioned via AWS Certificate Manager or HashiCorp Vault, the CI/CD pipeline should automatically extract the new SPKI hashes and inject them into the mobile app codebase for the next release.

More

Share This Insight

Related Posts