Implementing Certificate Pinning in a mobile application

TRUETECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Implementing Certificate Pinning in a mobile application
Complex
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing Certificate Pinning in mobile applications

Applications are intercepted via Charles Proxy or Frida — traffic is read, responses modified, APIs studied. This happens because TLS connection validates the certificate against system CAs, and the user (or pentester) simply added their CA to trusted. Certificate pinning closes this vector: the client accepts only a specific certificate or key, regardless of system certificate storage.

Certificate pinning vs public key pinning

Certificate pinning — client stores the full certificate (or its SHA-256 hash) and compares with what the server sent. Simple to implement, but the certificate changes with each renewal (typically every 1–2 years). If you forgot to update the pin before rotation — the app stops working for all users at once.

Public key pinning (HPKP-style) — pins the hash of SubjectPublicKeyInfo. When the certificate rotates, the private key stays the same, pin remains valid. This is the right choice for production. Additionally always keep a backup pin — hash of a backup key (may be from your CA), so if the main key is compromised you're not left without the ability to update clients.

iOS: NSURLSession + TrustKit

Native way via URLSessionDelegate:

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
    }

    // Extract public key from server certificate
    guard let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0),
          let serverKey = SecCertificateCopyKey(serverCert) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    let serverKeyData = SecKeyCopyExternalRepresentation(serverKey, nil)! as Data
    let serverKeyHash = sha256(data: serverKeyData)

    let pinnedHashes = ["base64encodedSHA256ofSubjectPublicKeyInfo=="]
    if pinnedHashes.contains(serverKeyHash) {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

For projects with multiple hosts, TrustKit is convenient — configured via Info.plist or dictionary, supports backup pins, reporting violations to endpoint.

Android: OkHttp CertificatePinner

Most Android projects use OkHttp for networking:

val certificatePinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAA...primaryKeyHash==")
    .add("api.example.com", "sha256/BBBBBBBBB...backupKeyHash==")
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

OkHttp itself calculates SHA-256 SubjectPublicKeyInfo and compares. On mismatch — SSLPeerUnverifiedException. Hashes can be obtained via openssl:

openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | openssl enc -base64

For Flutter — http_certificate_pinning package or native implementation via platform channels, since dart:io HttpClient doesn't support custom public key validation without wrappers.

Problems with pin updates and Debug builds

The main operational pain — key rotation. 30+ days before certificate replacement, release an update with new backup pin in the list, give users time to update, then rotate the key. This requires coordination between infrastructure and mobile teams.

In debug/QA builds, pinning is usually disabled so Charles/Proxyman works for testers. Done via BuildConfig.DEBUG flag or separate flavor without pinning config. Make sure release build in CI is assembled with pinning — otherwise you lose protection without noticing.

Bypassing Frida and root

Certificate pinning doesn't protect against attacks on rooted devices — Frida can hook SSLContext or hash comparison methods. That's a separate task (root detection + runtime integrity check). Pinning protects against proxy on non-rooted devices, covering most user interception scenarios.

Timelines

Setting up pinning on one host with correct backup-pin scheme — 2–3 days including testing, CI configuration for release build and documenting key rotation procedure.