HTTP Public Key Pinning (HPKP)

Using HTTPS helps preventing someone from snooping your username/password or hijacking your sessions. Using HSTS makes sure the connection stays on HTTPS, even if a MITM tries to redirect you to the plain HTTP version of a web site. But it is easier than you might think for a MITM to use a rogue certificate, making you believe everything is fine. HTTP Public Key Pinning (HPKP) helps the browser check that everything actually is fine.

The Certificate Authority (CA) Model

Certificates are issued by certificate authorities (CAs) who already have their root certificates installed in your OS or browser. All installed root certificates are treated equally with the same amount of trust, and are given ultimate trust – meaning if an installed root certificate signed a certificate, your computer will trust that certificate.

When your browser receives a certificate, or certificate chain, from a web site, it will check if it was signed by an installed root certificate. If it is, then it considers all good.

Do you see the issue here? Who decides which root certificates goes into your OS/browser, and which CAs are trusted? Both the governments in China and USA are represented, BTW.

Once in a while a certificate authority (CA) gets compromised and rogue certificates are issued. Even if you got your certificate from StartCom, computers will be happy with a certificate from Comodo – or the Hong Kong Post Office.

It is also incredibly easy to trick someone into installing a new root certificate. Free WiFi (yay!) is the most efficient, as you already are the MITM.


Pinning is the technique of telling your browser that only certificates with a specific public key somewhere in the certificate chain is to be trusted. Current implementations are based on Trust on First Use (TOFU), meaning your browser will have to trust the connection the first time you use a site. This is because the pinning info is sent via a HTTP header by the web server. In the future this can hopefully be retrieved via DNSSEC signed DNS records.

By sending the HTTP pinning header you are telling the browser to “only trust certificates with this public key somewhere in the chain”.

The browser will store the info it received on its first use and check against that info on later use.

What to pin?

You pin the public key of a certificate. With at least 3 certificates per site (site cert, intermediate cert, root cert) that gives you a few options on what to pin.

Keep in mind that your site cert will be replaced in the near future – within 3 years (used to be 5) – but most likely even before that due to reissues.

You have no control over what intermediate cert the CA will use on a reissue, so don’t pin that.

CAs might have multiple root certs in trust stores, and you simply can’t control which one they will use on reissues.

Rely on what you can control: Your own public key is the only thing in the certificate chain you can control. So that is what you want to pin.

HPKP doesn’t just let you, but requires you, to provide a backup public key. This is useful for when your private key gets compromised. Your backup key will then be used to generate a new certificate. The backup key should of course not ever be stored on the same infrastructure as your primary key and should never be used for anything else.

How to pin?

Technically, we’re not pinning the public key itself, but the Subject Public Key Information (SPKI) fingerprint, and we’re sending it Base64 encoded.

To get the Base64 encoded fingerprint of the SPKI for a private key, issue the following command (replace server.key with your own private key file):

openssl rsa -in server.key -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64

The last line of the output is what you want.

You can do this for as many keys as you need, but remember that you need at least the primary key you are using in production right now and a backup key stored somewhere else.

I have the following two fingerprints for my web site:

Primary: mpA8FLefHJeVrMEzwxCaj5Vo+h7EKMufcUYnN2Z66FU=
Backup: JSvx7a2q1pjkti0hGhOIPXfru36ORHjyFEawlBPuNxY=

The format of the header we want to send is:

Public-Key-Pins: pin-sha256="PRIMARY_KEY"; pin-sha256="BACKUP_KEY"; max-age=PIN_CACHE_EXPIRE_TIME; includeSubdomains; report-uri=""

The parameter max-age tells the browser for how long, in seconds, it should cache the pins. As explained in the RFC, you want this to be long enough to actually have a security impact, but short enough to let you replace the keys within a reasonable timeframe:

There is probably no ideal upper limit to the max-age directive that would satisfy all use cases. However, a value on the order of 60 days (5,184,000 seconds) may be considered a balance between the two competing security concerns.

Just like with HSTS, the includeSubdomains is optional, and should only be defined if you are using these keys for all subdomains as well. I am not, so I leave it out.

The report-uri is also optional and validation failures are reported to this URI.

So, I’m using this in my Nginx config:

add_header Public-Key-Pins 'pin-sha256="mpA8FLefHJeVrMEzwxCaj5Vo+h7EKMufcUYnN2Z66FU="; pin-sha256="JSvx7a2q1pjkti0hGhOIPXfru36ORHjyFEawlBPuNxY="; max-age=5184000';

If I was running Apache, it would look like this:

Header always set Public-Key-Pins "pin-sha256=\"mpA8FLefHJeVrMEzwxCaj5Vo+h7EKMufcUYnN2Z66FU=\"; pin-sha256=\"JSvx7a2q1pjkti0hGhOIPXfru36ORHjyFEawlBPuNxY=\"; max-age=5184000"

Browser support

At least Chromium based browsers and Firefox have support for HTTP Public Key Pinning, but note the following excerpt from the Chromium Security FAQ:

Chrome does not perform pin validation when the certificate chain chains up to a private trust anchor. A key result of this policy is that private trust anchors can be used to proxy (or MITM) connections, even to pinned sites. “Data loss prevention” appliances, firewalls, content filters, and malware can use this feature to defeat the protections of key pinning.

In other words: If someone tricks you into installing their root certificate (like for free WiFi), Chrome won’t validate the certificate with the pinned info. Use FireFox instead.

I assume more browsers will support HPKP soon. In any case, no browsers will break over this.

14 thoughts on “HTTP Public Key Pinning (HPKP)”

  1. Pinning your own public key is not the best idea. It might change or get compromised.
    By the way, it does not seem like you are using HPKP for your website right now.

    1. Bjørn Johansen

      It is for this exact reason you need the backup key. The backup key should ideally not ever be on your server. It should be created and stored somewhere else.

      I think it’s important to pin something you control. You can easily get in a situation where you need the CA to reissue your cert, but they can easily use a different intermediate, or even root, certificate to sign your new certificate. In that case, since you didn’t know of the other possible intermediates, you haven’t pinned them before the update and your site would be unavailable for up to 60 days (or whatever you use for max-age).

      I hope this helps clearifying why I do recommend pinning your own key, which you have control over.

      I do a lot of experiments with this site. But experimenting can be hard, when you lock down things too well. I removed my HPKP headers not long ago, but can’t do my experiments until the 60 days I used for max-age have passed.

  2. I’m confused. I used my privkey1.pem (Let’s Encrypt) and with

    openssl rsa -in privkey1.pem -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64

    I get only one key and I don’t know if it the primary or backup pin. say: “Incomplete Required backup pin(s) missing” create keys but without backup pin.

    Question: What is the right way to create a valid pin from Let’s Encrypt certificates?

    Thank you :-)

    1. Bjørn Johansen

      The key you get is the primary key. But remember that depending on your ACME client (Let’s Encrypt client), your private key will be regenerated each time you renew the cert. To use HPKP with Let’s Encrypt, you have to use an ACME client which let you provide your own CSR (the ACME protocol allows for this). I’ve planned to make a post on using HPKP with Let’s Encrypt for a while. Hopefully, I’ll have it out soon.

        1. Bjørn Johansen

          Thanks. I forgot all about it. It is actually really easy as long as you use an ACME client that supports custom CSRs. Certbot does that now too :-)

  3. I came here looking for redirecting https://ip-address to the domain name.
    I get pinged, curled and as many things as possible using the https://my-public-ip for which browsers throw your connection is not secure error. I have default ip resolving to domain with https but not this https with ip. I checked googles and many others including yours and the same error, The error code is: SSL_ERROR_BAD_CERT_DOMAIN, (Of course the certificate is only valid for the domain names it is issued for)
    Even though the topic of the post and my curiousity are somewhat different, could you share your insights if it is technically not possible to 301 (or any) to the https domain? (Of course, we can logic, if possible google or everyone would have implemented)
    I read that some CAs issue certificate to public IPs also but that practice may have come to an end.
    I already tried many different options to redirect through nginx server {..} block, none of them working though :(
    I hope you figured out what I’m trying to convey and I’ll be thankful to read your insights on this.
    Cheers from Kathmandu!

  4. Hi, I want to implement HTTP pinning in my tomcat 7+ web application, Which file i need to change for this configuration & what will be the configuration that i need to do in tomcat

    1. Bjørn Johansen

      Hi Sandeep.

      To answer that, I’d have to start researching what Tomcat actually is. I believe it’s a Java-thingie, maybe even a Java application server. But I really don’t know.

      So instead of asking me, I suggest you find an appropriate forum for that :-) Good luck!

  5. hai,

    i want to implement HPKP on nginx, but my CA is configured on AWS ELB,

    i tried to config the nginx but the HPKP still cannot deployed, i thought maybe is it because the CA not stored in nginx as well,

    any suggestion?.


    1. Bjørn Johansen

      Nah, it’s not that relevant. Turns out, people can’t be trusted with things they absolutely have to keep a backup of.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.