Article by Pushkar Deshmukh

Certificate Pinning in iOS (Part 2): Public Key Pinning Explained with Real-World Scenarios

Learn how to implement public key pinning in iOS with a practical, production-ready approach. Understand how it differs from certificate pinning, how it behaves during server updates, and how to design a resilient pinning strategy.

Pushkar Deshmukh

Pushkar Deshmukh

Senior iOS Engineer

April 4, 20264 min read5 views
iOSSwiftSecurityCertificate PinningPublic Key PinningURLSessionSSLTLSApp Security
Certificate Pinning in iOS (Part 2): Public Key Pinning Explained with Real-World Scenarios

In Part 1, we implemented certificate pinning using URLSession.

If you haven’t read it yet, I strongly recommend starting there—it builds the foundation.

Now, let’s solve the biggest real-world problem:

❗ What happens when your server certificate changes?

With certificate pinning:

  • Your app breaks immediately

  • You need to ship an app update

That’s not scalable.

👉 Enter Public Key Pinning — a more flexible and production-friendly approach.


🔐 What is Public Key Pinning?

Instead of pinning the entire certificate, we pin:

Only the public key of the certificate

Why this matters:

  • Certificates can change

  • But public keys often remain the same

👉 Result:

  • Your app continues working across certificate renewals


⚖️ Certificate Pinning vs Public Key Pinning

Feature

Certificate Pinning

Public Key Pinning

Breaks on cert renewal

❌ Yes

✅ No (usually)

Security level

High

High

Maintenance

High

Low

Flexibility

Low

High

👉 In real-world apps:

✅ Public key pinning is preferred


🧠 How Public Key Pinning Works

  1. Server sends certificate

  2. App extracts public key

  3. App compares with locally stored key

If match → ✅ allow
If not → ❌ block


🛠️ Implementation Using URLSession


📥 Step 1: Extract Public Key from Certificate

You first need the public key from your server certificate.

Option 1 (Recommended):

Use terminal:

openssl x509 -in server.cer -pubkey -noout > public_key.pem

📦 Step 2: Convert Public Key to Data (Hash it)

In production, we don’t store raw keys—we store a hash (usually SHA256).


🧩 Step 3: Create Pinning Delegate

import Foundation
import Security
import CommonCrypto

final class PublicKeyPinningDelegate: NSObject, URLSessionDelegate {
    
    private let pinnedKeyHashes: [Data] = [
        Data(base64Encoded: "YOUR_BASE64_HASH")!
    ]
    
    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
              let serverPublicKey = SecCertificateCopyKey(serverCertificate),
              let serverKeyData = SecKeyCopyExternalRepresentation(serverPublicKey, nil) as Data? else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        let serverKeyHash = sha256(data: serverKeyData)
        
        if pinnedKeyHashes.contains(serverKeyHash) {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
    
    private func sha256(data: Data) -> Data {
        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
        }
        return Data(hash)
    }
}

🌐 Step 4: Use It

let session = URLSession(
    configuration: .default,
    delegate: PublicKeyPinningDelegate(),
    delegateQueue: nil
)

🔄 What Happens When Server Certificate Updates?

This is where things get interesting.


🧪 Scenario 1: Certificate Renewed (Same Key)

  • Certificate expires → new certificate issued

  • Public key remains same

👉 Result:

✅ App continues working
❌ No app update needed


🚨 Scenario 2: Certificate Renewed (New Key)

  • New certificate

  • New public/private key pair

👉 Result:

❌ Pinning fails
❌ App breaks


🤔 So… Do We Need to Update the App?

👉 Yes — but only if public key changes

This is much less frequent than certificate renewal.


🧠 When Does Public Key Change?

Keep this section bookmarked 👇


🔁 Cases When Public Key MAY Change

  • Server migration (infra change)

  • Switching Certificate Authority

  • Security rotation policies

  • Key compromise (critical)

  • Moving to new encryption standards


🧷 Cases When Public Key DOES NOT Change

  • Normal certificate renewal

  • Expiry-based re-issue

  • Minor config updates


🛡️ Best Practice: Add Backup Keys (Very Important)

To avoid app breakage:

👉 Always include at least 2 keys:

  • Current key

  • Backup/future key


✅ Example

private let pinnedKeyHashes: [Data] = [
    Data(base64Encoded: "CURRENT_KEY_HASH")!,
    Data(base64Encoded: "BACKUP_KEY_HASH")!
]

💡 Strategy

  • Backend team shares:

    • Current public key

    • Next planned key

  • You ship both in app

👉 When rotation happens:

  • App still works without update


⚠️ Common Mistakes

❌ Pinning only 1 key

→ Risk of downtime

❌ Not coordinating with backend team

→ Leads to unexpected failures

❌ Using certificate pinning in scalable systems

→ High maintenance


🧪 How to Test

  • Use proxy tools (Charles / Proxyman)

  • Try intercepting traffic

👉 Expected:

  • Requests should fail if key doesn’t match


📊 When Should You Use Public Key Pinning?

Use it when:

  • You want strong security without frequent app updates

  • You control backend infrastructure

  • You can coordinate key rotation

Avoid it when:

  • Backend changes frequently without notice

  • You don’t have control over certificates


🔗 Recap

  • Certificate pinning → strict but fragile

  • Public key pinning → secure and flexible

✅ Best real-world choice: Public Key Pinning with backup keys


🧠 Final Thoughts

If you’re building a production-grade app:

  • Start with understanding (Part 1)

  • Move to public key pinning (this guide)

  • Always plan for rotation

Pushkar Deshmukh

Written by

Pushkar Deshmukh

Senior iOS Engineer

11+ years of experience building mobile and web applications. Passionate about Swift, React, and sharing knowledge through technical writing.

5views0comments

Comments

Loading…

Loading comments…

Read next

View all
Back to All Articles