Certificate Pinning is a mechanism for fixing a server’s certificate or public key, where an application uses a pre-known fingerprint to verify an HTTPS connection. Unlike the standard chain of trust through a CA, pinning ensures that even a compromised certificate authority cannot issue a fraudulent certificate for your domain. According to OWASP MSTG (2025), Certificate Pinning is listed as a mandatory control for L2 protection level applications. Implementation involves storing certificate hash fingerprints in code and verifying them on each request.
Key Takeaways
Certificate Pinning is a security technique where an application stores the fingerprint of a trusted certificate and uses it as the sole criterion for establishing an HTTPS connection. In the standard TLS model, the client verifies that the server’s certificate is signed by a trusted root CA — any of the hundreds of pre-installed certificate authorities on the system. Certificate Pinning replaces this chain with a direct check: the certificate must match the stored sample or contain the expected public key.
The problem with the standard model became evident after CA compromise incidents — DigiNotar (2011), Comodo (2011), TrustCor (2022). If a CA issues a fraudulent certificate for your domain, the browser or application accepts it as valid. Certificate Pinning prevents this attack: even a perfectly signed fraudulent certificate will be rejected because its fingerprint does not match the one pinned in the application.
The term pinning comes from pin — a fastener: the developer locks in a trusted certificate, and any deviation from it blocks the connection. According to research from Mitre CWE-295, improper certificate validation remains one of the top-10 most dangerous security errors in mobile applications, and Certificate Pinning is a direct method of preventing it.
Originally, Certificate Pinning was used in browsers through the HPKP (HTTP Public Key Pinning) mechanism, standardized in RFC 7469. The developer sent an HTTP Public-Key-Pins header with hashes of expected keys, and the browser stored them for a specified period. However, HPKP turned out to be dangerous: a single configuration error could lock a site out for months. In 2018, Chrome discontinued support for HPKP, and the current standard became client-side implementation — inside a mobile application or browser extension.
The Certificate Pinning process involves three key stages: fingerprint calculation, connection verification, and error handling. During preparation, the developer obtains the SHA-256 hash of the production server’s certificate or public key. For GDPR- and PCI DSS-compliant applications, it is also required to pin the fingerprints of intermediate CAs in the chain.
With each HTTPS request, the application intercepts the TLS authentication callback, extracts the server’s certificate, and computes its SHA-256 hash. This hash is compared against the stored list of trusted fingerprints. If a match is found — the connection proceeds. If not — the application must terminate the connection and report an error without revealing implementation details to an attacker.
fun validateCertificate(certificate: X509Certificate,
expectedHash: String): Boolean {
val digest = MessageDigest.getInstance("SHA-256")
val hash = Base64.encodeToString(
digest.digest(certificate.publicKey.getEncoded()),
Base64.DEFAULT
).trim()
return hash == expectedHash
}
The function takes an X509Certificate object from the server and the expected hash. It first extracts the certificate’s public key, computes the SHA-256 hash, and encodes it in Base64. The result is compared against the expected fingerprint. In production, you should add verification against an array of 2–3 fingerprints to support rotation.
When implementing pinning, you need to choose which cryptographic object to lock in. Certificate Pinning binds to the X.509 certificate itself — its serial number, validity period, and the entire chain. Public Key Pinning locks only the public key inside the certificate, ignoring other fields. This choice significantly impacts operational costs.
| Criterion | Certificate Pinning | Public Key Pinning |
|---|---|---|
| Pinning Object | Entire X.509 certificate | RSA/ECDSA public key |
| Rotation | Requires update on each reissuance | Does not change when the certificate is renewed with the same key |
| Security | Maximum precision binding | Less sensitive to details |
| Flexibility | Low — certificates change every 1–2 years | High — keys can last 5–10 years |
| Recommendation | For critical systems with controlled updates | For most mobile applications and APIs |
Public Key Pinning is the preferred choice for most projects. Server public keys generally remain unchanged when a certificate is reissued — the company simply signs the old key with a new certificate. This means the application does not require an update after a certificate change if the key pair has not changed. Certificate Pinning, on the other hand, is recommended for scenarios where the developer fully controls both the server and client code, such as in enterprise applications with a strict update cycle.
TOFU is a strategy where Certificate Pinning is not configured in advance but remembers the certificate on the first connection to the server. This approach is convenient for applications that do not know in advance which server they will connect to. The downside is vulnerability to an initial attack: if the first connection is intercepted, a fraudulent certificate will be accepted as trusted. TOFU is used in SSH connections and some P2P protocols.
On both platforms, Certificate Pinning is implemented by intercepting the TLS connection at the network stack level. On iOS, the URLSession delegate or Alamofire ServerTrustManager is used. On Android, the preferred method is OkHttp CertificatePinner, which is built into popular HTTP clients and supports configuring multiple fingerprints for each domain.
func validate(serverTrust: SecTrust,
pinnedHash: String) -> Bool {
guard let certificates = SecTrustCopyCertificateChain(serverTrust)
as? [SecCertificate] else { return false }
for certificate in certificates {
let data = SecCertificateCopyData(certificate)
var hash = Data(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
CC_SHA256($0.baseAddress,
CC_LONG(data.count), &hash)
}
if hash.base64EncodedString() == pinnedHash {
return true
}
}
return false
}
In the Swift function, the certificate chain is extracted from serverTrust, a SHA-256 hash is computed for each certificate, and the result is compared against the expected one. Iterating through all certificates in the chain allows pinning at the intermediate CA level — if an intermediate certificate matches, the connection is accepted. This provides flexibility during leaf certificate rotation.
If the application does not use OkHttp, Certificate Pinning can be implemented through a custom X509TrustManager. This approach requires more code but gives full control over the verification process. The TrustManager overrides the checkServerTrusted method, where the developer manually verifies the server certificates and decides whether to trust them. It is recommended only for specific scenarios where the OkHttp library is unavailable.
The most common mistake is the absence of backup pins. A developer includes a single certificate fingerprint, and when it expires, users massively lose connectivity. The minimum acceptable configuration is two fingerprints: the current certificate and a backup. Ideally three: the current one, a backup, and the root CA fingerprint as a fallback.
The second mistake is storing pins in plain text in code. An attacker with access to an APK or IPA can easily extract and replace fingerprints. Hash obfuscation is recommended: split the string into parts, store in encrypted resources, or compute at runtime. For Android, ProGuard with string constant obfuscation is effective.
The third mistake is pinning at the development certificate level. Development and production certificates are usually different, but developers often forget to switch pins when building a release. The result is that the production application cannot connect to the server. The solution is separate pin configurations for debug and release via BuildConfig or flavor-specific resources.
Frequently Asked Questions
SSL Pinning is a general term for binding to an SSL/TLS certificate. Certificate Pinning is a specific implementation that locks in the X.509 certificate itself, not just the public key. The difference is in the binding object: certificate vs key.
It is recommended to store hashes in resources with obfuscation via ProGuard (Android) or encrypted through Keychain (iOS). Avoid storing pins in plain text in strings.xml or Info.plist without encryption.
With every certificate change on the server. It is recommended to add a new fingerprint as a backup pin 3–6 months before the current one expires, and remove the old one after rotation. At least one backup pin is mandatory.
Yes, through conditional compilation: pinning is disabled in the debug build and enabled in the release build. Use BuildConfig.DEBUG on Android or #if DEBUG on iOS for switching. Never do this through a runtime flag accessible to the user.
Immediately release an application update with new fingerprints and publish it in the stores. Use a forced update mechanism. If the backup pins included the backup CA fingerprint, you can temporarily switch to a different domain with a different certificate.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also