Mobile App Certificate Pinning: The DevOps Guide to Preventing MitM Attacks Without Bricking Your App

In the modern mobile landscape, the connection between your user’s device and your backend API is the lifeline of your business. According to the *2024 State of API Security Report* by Imperva, 27% of...

Tim Henrich
February 23, 2026
8 min read
69 views

Mobile App Certificate Pinning: The DevOps Guide to Preventing MitM Attacks Without Bricking Your App

In the modern mobile landscape, the connection between your user’s device and your backend API is the lifeline of your business. According to the 2024 State of API Security Report by Imperva, 27% of all API attacks now target mobile endpoints. The threat is real: attackers using public WiFi hotspots, compromised routers, or malicious proxies to inspect traffic, steal tokens, and scrape data via Man-in-the-Middle (MitM) attacks.

The standard defense against this has long been Certificate Pinning. By hardcoding trust for a specific certificate within the app, you ensure that the app communicates only with your specific server, rejecting even valid certificates issued by trusted Certificate Authorities (CAs) if they aren't your certificate.

However, for DevOps engineers and IT administrators, certificate pinning is a double-edged sword. Implemented correctly, it is a fortress. Implemented poorly, it is a "bricking" mechanism that can lock millions of users out of your app instantly, with no way to push an update to fix it.

This guide covers the technical implementation of certificate pinning in 2025, the shift toward Subject Public Key Info (SPKI) pinning, and how to manage the operational risks using tools like Expiring.at.


The Core Concept: What Are We Actually Pinning?

To understand how to implement pinning safely, we must first agree on what layer of the certificate chain we are trusting. A typical SSL/TLS handshake involves a chain of trust:

  1. Root Certificate: Owned by the CA (e.g., DigiCert, ISRG Root X1). Long-lived (10-20 years).
  2. Intermediate Certificate: Issues the leaf certificates. Medium lifespan.
  3. Leaf Certificate: Your domain's certificate (e.g., api.yourcompany.com). Short lifespan (90 days to 1 year).

The Mistake: Pinning the Leaf Certificate

In the past, developers often pinned the Leaf Certificate. This is now considered an anti-pattern. With the rise of automated certificate management (ACME) and authorities like Let's Encrypt, certificates rotate automatically every 60 to 90 days.

If you pin the specific Leaf Certificate in your app binary, your app will break the moment your server certificate renews. Since you cannot force every user to update their app instantly, this guarantees service outages.

The Solution: Pinning the SPKI (Subject Public Key Info)

The industry standard in 2025 is to pin the Subject Public Key Info (SPKI). This is a hash of the public key contained within the certificate.

Unlike the certificate itself (which contains expiration dates and metadata that change upon renewal), the public key can remain static across multiple renewals. You can generate a Certificate Signing Request (CSR) using the same private key repeatedly. This allows you to rotate your server's certificate for validity purposes without changing the public key hash pinned inside the mobile app.


Platform-Specific Implementation

Modern mobile operating systems have moved away from "programmatic" pinning (writing custom code to check hashes) toward "declarative" pinning (configuration files). This reduces the surface area for implementation bugs.

1. Android: Network Security Configuration

Since Android 7.0 (API level 24), Google recommends using the Network Security Configuration file. This allows you to define pinning logic in XML without touching Java or Kotlin code.

Step 1: Create a file named network_security_config.xml in your res/xml/ directory.

Step 2: Define your domain and pins.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">api.yourcompany.com</domain>
        <pin-set expiration="2025-12-31">
            <!-- Primary Pin: The hash of your current public key -->
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>

            <!-- BACKUP PIN: The hash of a key you keep offline -->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldWDURappCnOgQWo3sKI=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

Step 3: Reference this file in your AndroidManifest.xml.

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

Why this works: Android handles the validation natively. If the server presents a chain that doesn't match one of the hashes in the <pin-set>, the connection is terminated immediately.

2. iOS: TrustKit and App Transport Security

While Apple provides App Transport Security (ATS) for pinning via Info.plist, it is notoriously rigid and lacks reporting capabilities. If a pin fails, the connection drops silently, leaving you wondering if it's a hack or a bug.

Most iOS security teams prefer using TrustKit, an open-source library that wraps iOS SSL validation and adds reporting features.

Implementation with TrustKit:

First, initialize TrustKit in your AppDelegate:

import TrustKit

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    let trustKitConfig = [
        kTSKSwizzleNetworkDelegates: false,
        kTSKPinnedDomains: [
            "api.yourcompany.com": [
                kTSKEnforcePinning: true,
                kTSKIncludeSubdomains: true,
                kTSKPublicKeyAlgorithms: [kTSKAlgorithmRsa2048],
                kTSKPublicKeyHashes: [
                    "7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=", // Current Key
                    "fwza0LRMXouZHRC8Ei+4PyuldWDURappCnOgQWo3sKI="  // Backup Key
                ],
                kTSKReportUris: ["https://your-reporting-endpoint.com/log"]
            ]
        ]
    ] as [String : Any]

    TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
    return true
}

This configuration enables you to receive a JSON report whenever a validation failure occurs, allowing you to distinguish between an active attack and a misconfiguration.

3. Cross-Platform: Flutter

For Flutter applications, the standard HttpClient does not support pinning out of the box in a declarative way. You typically use the dio package or override the global HttpOverrides.

import 'dart:io';

class PinnedHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    final client = super.createHttpClient(context);
    client.badCertificateCallback = (X509Certificate cert, String host, int port) {
      // Implement hash verification logic here
      // Return true ONLY if the SHA-256 of cert.der matches your pinned hash
      return false; 
    };
    return client;
  }
}

Note: While manual implementation is possible in Flutter, native bridging to Android (Network Security Config) and iOS (TrustKit) is often recommended for higher security standards.


The "Bricking" Nightmare: A DevOps Reality Check

The single biggest risk in certificate pinning is the Operational Lockout.

Imagine this scenario:
1. You pin your Leaf Certificate's public key in the app.
2. Your DevOps team sets up auto-renewal on the load balancer.
3. The load balancer renews the certificate but generates a new key pair (default behavior for many ACME clients).
4. The server starts serving the new key.
5. Result: Every installed instance of your app rejects the server. Users cannot log in. Worse, they cannot download an app update because the app needs to connect to the server to check for feature flags or force-update requirements.

The "Rule of Two": Always Have a Backup

To prevent this, you must adhere to the Rule of Two. You should never release an app with only one pin.

  1. Pin 1 (Active): The hash of the key currently live on your server.
  2. Pin 2 (Backup): The hash of a key pair you have generated, saved to a secure offline location (like a password manager or vault), and have not yet used.

If your active key is compromised or accidentally rotated, you can immediately take your Backup Key, generate a certificate for it, and deploy it to your load balancer. The app will trust it immediately because the hash is already in the binary.


Monitoring and Management: The Missing Link

Pinning is not a "set it and forget it" configuration. It requires active monitoring of your certificate infrastructure. If your server certificate expires, or if a CDN changes the certificate issuance logic without your knowledge, your pins become invalid.

This is where proactive monitoring becomes part of your security posture. You need to know exactly when your certificates are expiring and verify that the new certificates match your pinned expectations before they go live.

Using a dedicated monitoring tool like Expiring.at is essential for teams managing pinned applications. Expiring.at allows you to:
* Track the expiration dates of all your SSL/TLS certificates across your API endpoints.
* Receive notifications via Slack, Email, or SMS weeks before a renewal is needed.
* Prevent the scramble of emergency rotations that often lead to mismatched keys and broken pins.

By integrating expiration tracking into your workflow, you ensure that the DevOps team (managing the certs) and the Mobile team (managing the pins) are synchronized.


Security vs. Reality: Addressing Reverse Engineering

It is important to address the elephant in the room: Frida.

Tools like Frida and Objection allow security researchers (and attackers) to hook into a running application on a rooted or jailbroken device and disable SSL pinning at runtime. A simple script can intercept the checkServerTrusted call and force it to return true.

Does this mean pinning is useless? No.

Pinning is part of a Defense-in-Depth strategy.
1. It stops automated attacks: It prevents dragnet surveillance and automated botnets from scraping your API easily.
2. It raises the bar: It forces an attacker to have a rooted device, specific tooling, and the skill to reverse engineer your specific app.
3. Obfuscation helps: Using tools like ProGuard (Android) or iXGuard makes it significantly harder for attackers to find where the pinning logic resides to hook it.

For high-security applications (FinTech, Healthcare), consider combining pinning with RASP (Runtime Application Self-Protection) to detect if the device is rooted or if Frida is present, and shut down the app if detected.


Conclusion: Best Practices Checklist

Implementing certificate pinning is a commitment to operational discipline. If you are ready to implement it, ensure you follow this checklist:

  1. Pin the SPKI, not the Certificate: Allow your certificates to rotate without breaking the app.
  2. Always use Backup Pins: Keep a "Break Glass" key offline.
  3. Fail Closed for High Security: If the pin fails, the connection must terminate.
  4. Implement Reporting: Use tools like TrustKit to know when pinning fails in the wild.
  5. Monitor Expiration: Use Expiring.at to ensure your server-

Share This Insight

Related Posts