Security

This page dives into the technical details of how our QR codes work and are secured.
If you're looking for a less-technical overview of how our system works, check out the how it works page.


Vax.Codes is a very simple public key infrastructure system that makes it easy for locally trusted organizations to issue signed QR codes to people who they've verified to have received a Covid-19 vaccine. Organizations that generate these QR codes are called "issuers", and communities can also create "groups" of issuers they trust.

Issuers sign QR codes using a "keyfile", which is a public/private OpenPGP keypair generated either in our browser tool or locally. Signed QR codes contain some sort of identifying information about the person being verified (e.g. their name), the issuer's ID, and a cryptographic signature proving the issuer verified the identifying information.

QR codes are then scanned by an event organizer or business who wants to verify if a person has received Covid-19 vaccine. The QR message itself is just a URL to our scan page with the signature + issuer_id + message as a URL fragment (e.g. https://vax.codes/scan#v1/some_id/...signature.../Joe%20Smith...).

When the scan page loads, the page's javascript checks the URL fragment for something that looks like our verification code format. If present, it tries to load the issuer's public key from the "issuers API" and validate the signature in the code. If the signature validates, the signed message (e.g. the verified person's name) is displayed, along with links to the issuer's profile and any groups the issuer is in.

By default, issuers and groups are untrusted, so it is up to the event organizer or business doing the scanning to have a list of issuers they trust to properly issue QR codes to vaccine recipients. In general, the expectation is that a local event organizer or business personally knows and trusts the issuer or group owner (e.g. a local community center).

The goal of this project is not become a global vaccine verification system (e.g. "vaccine passports"), but rather focus on helping local communities get a short-term system in place for verifying local community members have been vaccinated so that in-person gathers can safely resume. Eventually, the expectation is that local Vax.Codes issuers and groups will be replaced by more robust, sophisticated, government-run vaccine verification infrastructure.


This project is nothing more than a static website with a list of registered issuers and groups hardcoded into the static issuers API. All keyfile generation, QR code issuing, and QR code scanning are done entirely client-side in javascript, so no private key or QR code contents are ever passed to our servers.

The QR codes themselves are the things that contain the signature and contents of the signed message, so there's no need for us to run a database or have any record of who has been issued a QR code. Only the issuers of those QR codes knows what's in them and who they issued them to.

So this project NEVER stores or even receives any personal or medical information about people who have been issued QR codes. Our goal is simply to make it easy for local community issuers to succeed in setting up a local vaccine verification system for event organizers and businesses.

Finally, this project is completely open source and available from our Github repo , so anyone can fork our codebase and self-host their own system if they don't trust us.


Our QR code format is just a simple URL that links to a code verification page that can verify the code. That way, any scanner app will be able to open the link in a browser and verify the contents as signed by the issuer.

Our QR code URLs have the following format:

{scan_url}#{version}/{issuer_id}/{signature}/{message}

The {scan_url} a link to a scanner website that will know how to interpret the code (i.e. https://vax.codes/scan).

The {version} is the format version of the code (currently v1).

The {signature} is the detached OpenPGP signature using the issuer's keypair encoded as a url-safe base64 string (since detacted signatures are binary data).

The {message} is the percent-encoded contents that the issuer wants to include in the QR code (usually the name of the person they verified, e.g. "John Smith").

For example, this QR code:

will scan as this URL:

https://vax.codes/scan#v1/test_arc/iF4EABMIAAYFAmApt2gACgkQ0ldpWsVGpsTl8QD_cnfQCpJC2QZaLDT7i8o_uXu7pCWVuNI7n5hvwcxRAf4A_3fwIqNIcEZZ-O3lsSVubq2yccUskpgeXqTU0DcDsfG8/Hello%20World

Open this link


The project is nothing more than a Jekyll static website hosted on Github pages .

Our issuers API and groups API are just statically generated json endpoints on the same static website as our other pages.

Issuers and groups are manually added to the _issuers and _groups collections in our repo whenever a new issuer requests to register or a group is created.

Both issuer registration and group creation requests are done by emailing the request directly from the propsective issuer or group owner's own email, so all we need to do is have an email address where they can send the request. Also, the request for issuers to join a group is done via email, and updates to an issuer's groups is done manually after we receive an email from a group owner approving the membership request.

By using email as the issuer/group communication mechanism and hardcoding issuers and groups into our static website repository, we can run a very simple, secure, and scalable public key infrastructure that is also easy to clone and self-host.


Have feedback or thoughts on this security document? Let us know!