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
Senior iOS Engineer

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
Server sends certificate
App extracts public key
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




Comments
Loading comments…