SPKI Pin Generator
Both kinds of pin, and a straight answer about which to use. A key pin survives renewal if the key is reused; a certificate pin binds tighter and breaks every time.
What a pin is
A pin is a SHA-256 hash of something a server presents, stored inside an application and checked on every connection. If the value does not match, the connection is refused — whatever the system trust store says about it.
It exists to close a specific hole: the ordinary trust model accepts a certificate from any of the hundred-odd CAs your device trusts. Pinning narrows that to one key you chose.
Two things you can pin, and they are not equivalent
This tool produces both. Which one you choose decides whether pinning is something you maintain or something that maintains you.
Public key pin (SPKI)
Hashes only the SubjectPublicKeyInfo — the key and its algorithm — and ignores everything else in the certificate.
- Survives renewal. A certificate is reissued; the key need not change. With
certbot --reuse-key, or by reusing the same CSR, the pin keeps matching indefinitely. - Can be computed before the certificate exists, from a CSR or a private key. That is the only way to prepare a backup pin for a key you have not deployed yet.
- The only form the declarative configs accept — Android's
network_security_config, OkHttp'sCertificatePinner,curl --pinnedpubkey. - Against: it is blind to everything but the key. A certificate reissued for that same key with different names, a different CA, or a much longer validity still matches.
- Against: if the key itself leaks, the pin does not help — it will accept the attacker's certificate quite happily. Recovering means burning the pin, which is what the backup exists for.
Certificate pin
Hashes the entire DER certificate. It is the same value your browser calls the SHA-256 fingerprint, shown as hex there and base64 here.
- Tighter. Binds to one exact certificate — key, names, issuer and validity window together. A reissue for the same key does not match.
- Fails closed on a known date. When the certificate expires the pin stops matching, so a pin nobody remembers cannot quietly outlive what it was protecting.
- Against: it breaks at every renewal without exception. The serial number and the dates change even when the key does not — and with lifetimes heading to 47 days, that means an app release every six weeks, indefinitely.
- Against: it cannot be prepared in advance, because the certificate does not exist until the CA issues it. There is no way to pre-pin a backup.
- Against: unsupported by the declarative configs. You write the comparison yourself, in a trust manager or a
URLSessiondelegate — which is exactly where pinning bugs live.
Which to use
Pin the key for anything shipped to people you cannot update on demand. That is almost every mobile app.
Pin the certificate only when both ends are yours and change together: a device talking to one fixed endpoint, or an internal service running a long-lived self-signed certificate you rotate deliberately. In those cases the tighter binding is worth the coupling, because the renewal is your own deployment anyway.
The pin is the base64, and nothing else
The value itself is just the digest: D18c7440RjXoF6av9C2BHoEQFXoFpM8cYjhsJGvx4sE=. Everything you see wrapped around it belongs to one particular library, and using the wrong wrapper is the most common way a perfectly correct pin gets rejected at runtime.
| Where it goes | What to write |
|---|---|
Android network_security_config | the bare base64, inside <pin digest="SHA-256"> |
| iOS / TrustKit | the bare base64 |
OkHttp CertificatePinner | sha256/ then the base64 — one slash |
curl --pinnedpubkey | sha256// then the base64 — two slashes |
The results panel prints each of these next to the pin, so there is nothing to remember.
Why there is no SHA-1 option
Reasonable question, because SHA-1 is everywhere around certificates. Your browser's certificate viewer prints a SHA-1 fingerprint. So does keytool -list. And Firebase and the Google Cloud console ask for "the SHA-1 fingerprint" when you register an Android app — which is the likeliest source of the confusion, and is a different thing entirely: that identifies your app signing certificate to Google's APIs, and has nothing to do with pinning a server's certificate on the network.
For pinning itself, SHA-256 is what the implementations take. HPKP only ever allowed pin-sha256. Android's network_security_config documents SHA-256 as the only supported digest. OkHttp will still accept a sha1/ prefix, but deprecates it.
And it should be deprecated: SHA-1 collisions have been practical since 2017, and chosen-prefix collisions since 2020 at a cost in the tens of thousands of dollars. For a pin that matters — an attacker who can produce two certificates sharing a hash gets you to pin one and presents the other. It is an expensive attack and not a likely one, but there is nothing to weigh against it, because SHA-256 is supported everywhere SHA-1 is.
The backup pin is not optional
Pinning converts a key loss into an outage you cannot fix from your side. If the pinned key is lost, compromised, or has to be rotated in a hurry, every copy of your app that shipped with that single pin stops connecting — and it keeps failing until each user installs an update. Some never will.
So pin at least two keys: the one in use, and a spare generated now, kept offline, and not deployed anywhere. When the first has to go, the second is already trusted by every client in the field. This tool will not let you paste the same key twice.
Where pins actually work
- Android —
network_security_config.xml, with anexpirationdate after which pinning is ignored. Set one: it is the safety catch that stops an abandoned app from locking itself out permanently. - OkHttp —
CertificatePinner, the same values with asha256/prefix. - iOS — TrustKit or a manual
URLSessiondelegate; Apple provides no declarative equivalent.
Not in browsers. The Public-Key-Pins header (HPKP) was removed from every major browser by 2018. Too many sites pinned a key they then lost and made their own domain unreachable for months, with no way to undo it. If you find a tutorial recommending the header, it is out of date.
Common problems
- The pin stops matching after renewal. Your CA or ACME client generated a fresh key. Certbot does this by default —
--reuse-keykeeps the existing one. - It works in the emulator and fails on a device. Usually a proxy or corporate MITM appliance in the path, which is pinning doing exactly its job.
- Pinning an intermediate or root instead. Legitimate and more forgiving, but it only narrows trust to that one CA rather than to your key. Decide which you actually want.