EU Covid-19 Vaccine Certificate (Health Pass) QR Code Reader in Python 3


GeeXLab - Health Pass / Covid-19 vaccine certificate QR Code Reader in Python 3



Downloads

How to run the demo?
Download and unzip GeeXLab where you want, launch GeeXLab and drop the demo (main.xml) in GeeXLab. The full version of GeeXLab for Windows is recommended for this demo.

The EU health pass (or Covid-19 vaccine certificate) is a document that contains some information about you (essentially your name and date of birth) as well as your current vaccine status (number of doses) and a big QR code that gathers all data. Here is a small demo coded in Python 3 that scans the QR code of the EU vaccine certificate, decodes it and shows you the data hidden behind this QR code.

The demo works on Windows only because of the built-in webcam functions I used for scanning QR codes. If necessary, the demo can be updated later to bypass the webcam and scan the image of a QR code you dropped in. You will be able then to run the demo on Linux too.

Moreover, the full GeeXLab for Windows contains all Python3 packages required by the demo. Just download the full version of GeeXLab, unzip it, run GeeXLab and drop the QR code demo in GeeXLab. A serious webcam is also required. A webcam that can do a clear focus on very close objects like your health pass QR code (on your mobile phone or on paper). Some tries (on the button SCAN a QR Code) will be likely necessary to read the QR code.

The core of the QR code decoding step has been adapted from this source and can be summed up to this:

– the QR code is a string starting with HC1:
– the string following HC1: is base45 encoded.
– the decoded base45 string leads to zlib-compressed data.
– the decompression leads to a CBOR Web Token structure.

To decode the QR code, you need the following Python 3 packages:
– base45
– cbor2
– zlib
– pprint

base45 and cbor2 packages be installed using pip:

pip install base45
pip install cbor2

base45 and cbor2 are already shipped with the full version of GeeXLab so don’t worry about installing them.

zlib and pprint are shipped with Python 3.

Here is the decoding function of the demo you can find in the frame.py file:

def DecodeHealthPass(payload):
  # The HC1: prefix nust be removed before decoding.
  s = payload.replace("HC1:", "")

  # decode Base45
  b45payload = base45.b45decode(s)

  # decompress using zlib
  cbordata = zlib.decompress(b45payload)
  
  # load the CBOR structure
  decoded = cbor2.loads(cbordata)
  decoded_qrcode = cbor2.loads(decoded.value[2])

  # prepare the CBOR structure in a readable way (newlines are added). 
  decoded_qrcode_str = pprint.pformat(decoded_qrcode)
  return decoded_qrcode_str

 
The demo:
GeeXLab - Health Pass / Covid-19 vaccine certificate QR Code Reader in Python 3

 
Here are the two steps to display the decoded QR code:

1/ click on SCAN a QR Code
This first step will show you the content (payload) of the QR code that must start with HC1: to be a valid health pass. You can now copy the payload to the clipboard if you need it.

2/ click on the Show decoded health pass button
This second step will decode the payload and display it in a small window. You can also copy to the clipboard the decoded health pass.

The decoded health pass contains information about the number of doses, the type of vaccin and its manufacturer. Here is a little help to decrypt these fields:

Field: ci --> "Unique Certificate Identifier, UVCI"
Field: co --> "Country of Test"
Field: is --> "Certificate Issuer"

Field: dob --> "Date of Birth, ISO 8601"

Field: dn --> "dose number"
Field: sd --> "Number of doses"

Field: mp  (medicinal product)
  EU/1/20/1528 --> Comirnaty
  EU/1/20/1507 --> COVID-19 Vaccine Moderna
  EU/1/21/1529 --> Vaxzevria
  EU/1/20/1525 --> COVID-19 Vaccine Janssen
 
Field: ma (vaccine manufacturer)
  ORG-100030215 --> Biontech Manufacturing GmbH
  ORG-100001699 --> AstraZeneca AB
  ORG-100001417 --> Janssen-Cilag International
  ORG-100031184 --> Moderna Biotech Spain S.L.
  ORG-100006270 --> Curevac AG

I added a PDF file in the demo folder that contains the specifications of the EU Covid-19 vaccine certificate.





2 thoughts on “EU Covid-19 Vaccine Certificate (Health Pass) QR Code Reader in Python 3”

  1. Rio

    {
    “ver”: “1.3.0”,
    “dob”: “1888-02-13”,
    “nam”: {
    “fn”: “Bananas”,
    “fnt”: “BANANAS”,
    “gn”: “Manas”,
    “gnt”: “MANAS”
    },
    “t”: [
    {
    “co”: “IE”,
    “tg”: “840539006”,
    “tt”: “LP6464-4”,
    “tr”: “260415000”,
    “tc”: “National Covid center (fake for a test)”,
    “sc”: “2022-01-04T00:56:21Z”,
    “nm”: “Fake test ehealth.vyncke.org”,
    “is”: “Ministry of Magic Health, Whitehall, London”,
    “ci”: “URN:UVCI:01:IE:77Z75Z244Z135”
    }
    ]
    }

    The set of to-be-signed COSE claims is now:

    * -260: health claims
    * 1: issuer
    * 4: expiration time
    * 6: issued at

    Which can be displayed as the JSON object:
    {
    “-260”: {
    “1”: {
    “dob”: “1888-02-13”,
    “nam”: {
    “fn”: “Bananas”,
    “fnt”: “BANANAS”,
    “gn”: “Manas”,
    “gnt”: “MANAS”
    },
    “t”: [
    {
    “ci”: “URN:UVCI:01:IE:77Z75Z244Z135”,
    “co”: “IE”,
    “is”: “Ministry of Magic Health, Whitehall, London”,
    “nm”: “Fake test ehealth.vyncke.org”,
    “sc”: “2022-01-04T00:56:21Z”,
    “tc”: “National Covid center (fake for a test)”,
    “tg”: “840539006”,
    “tr”: “260415000”,
    “tt”: “LP6464-4”
    }
    ],
    “ver”: “1.3.0”
    }
    },
    “1”: “IE”,
    “4”: 1642035382,
    “6”: 1641948982
    }

    After CBOR compression/encoding, it is now:
    A4 01 62 49 45 04 1A 61 DF 78 B6 06 1A 61 DE 27 . . b I E . . a . x . . . a . ‘
    36 39 01 03 A1 01 A4 63 76 65 72 65 31 2E 33 2E 6 9 . . . . . c v e r e 1 . 3 .
    30 63 64 6F 62 6A 31 38 38 38 2D 30 32 2D 31 33 0 c d o b j 1 8 8 8 – 0 2 – 1 3
    63 6E 61 6D A4 62 66 6E 67 42 61 6E 61 6E 61 73 c n a m . b f n g B a n a n a s
    63 66 6E 74 67 42 41 4E 41 4E 41 53 62 67 6E 65 c f n t g B A N A N A S b g n e
    4D 61 6E 61 73 63 67 6E 74 65 4D 41 4E 41 53 61 M a n a s c g n t e M A N A S a
    74 81 A9 62 63 6F 62 49 45 62 74 67 69 38 34 30 t . . b c o b I E b t g i 8 4 0
    35 33 39 30 30 36 62 74 74 68 4C 50 36 34 36 34 5 3 9 0 0 6 b t t h L P 6 4 6 4
    2D 34 62 74 72 69 32 36 30 34 31 35 30 30 30 62 – 4 b t r i 2 6 0 4 1 5 0 0 0 b
    74 63 78 27 4E 61 74 69 6F 6E 61 6C 20 43 6F 76 t c x ‘ N a t i o n a l C o v
    69 64 20 63 65 6E 74 65 72 20 28 66 61 6B 65 20 i d c e n t e r ( f a k e
    66 6F 72 20 61 20 74 65 73 74 29 62 73 63 74 32 f o r a t e s t ) b s c t 2
    30 32 32 2D 30 31 2D 30 34 54 30 30 3A 35 36 3A 0 2 2 – 0 1 – 0 4 T 0 0 : 5 6 :
    32 31 5A 62 6E 6D 78 1C 46 61 6B 65 20 74 65 73 2 1 Z b n m x . F a k e t e s
    74 20 65 68 65 61 6C 74 68 2E 76 79 6E 63 6B 65 t e h e a l t h . v y n c k e
    2E 6F 72 67 62 69 73 78 2B 4D 69 6E 69 73 74 72 . o r g b i s x + M i n i s t r
    79 20 6F 66 20 4D 61 67 69 63 20 48 65 61 6C 74 y o f M a g i c H e a l t
    68 2C 20 57 68 69 74 65 68 61 6C 6C 2C 20 4C 6F h , W h i t e h a l l , L o
    6E 64 6F 6E 62 63 69 78 1C 55 52 4E 3A 55 56 43 n d o n b c i x . U R N : U V C
    49 3A 30 31 3A 49 45 3A 37 37 5A 37 35 5A 32 34 I : 0 1 : I E : 7 7 Z 7 5 Z 2 4
    34 5A 31 33 35 4 Z 1 3 5
    Using a dummy signature

    After COSE signature and CBOR encoding:
    D2 84 4D A2 01 26 04 48 00 00 00 00 00 00 00 00 . . M . . & . H . . . . . . . .
    A0 59 01 45 A4 01 62 49 45 04 1A 61 DF 78 B6 06 . Y . E . . b I E . . a . x . .
    1A 61 DE 27 36 39 01 03 A1 01 A4 63 76 65 72 65 . a . ‘ 6 9 . . . . . c v e r e
    31 2E 33 2E 30 63 64 6F 62 6A 31 38 38 38 2D 30 1 . 3 . 0 c d o b j 1 8 8 8 – 0
    32 2D 31 33 63 6E 61 6D A4 62 66 6E 67 42 61 6E 2 – 1 3 c n a m . b f n g B a n
    61 6E 61 73 63 66 6E 74 67 42 41 4E 41 4E 41 53 a n a s c f n t g B A N A N A S
    62 67 6E 65 4D 61 6E 61 73 63 67 6E 74 65 4D 41 b g n e M a n a s c g n t e M A
    4E 41 53 61 74 81 A9 62 63 6F 62 49 45 62 74 67 N A S a t . . b c o b I E b t g
    69 38 34 30 35 33 39 30 30 36 62 74 74 68 4C 50 i 8 4 0 5 3 9 0 0 6 b t t h L P
    36 34 36 34 2D 34 62 74 72 69 32 36 30 34 31 35 6 4 6 4 – 4 b t r i 2 6 0 4 1 5
    30 30 30 62 74 63 78 27 4E 61 74 69 6F 6E 61 6C 0 0 0 b t c x ‘ N a t i o n a l
    20 43 6F 76 69 64 20 63 65 6E 74 65 72 20 28 66 C o v i d c e n t e r ( f
    61 6B 65 20 66 6F 72 20 61 20 74 65 73 74 29 62 a k e f o r a t e s t ) b
    73 63 74 32 30 32 32 2D 30 31 2D 30 34 54 30 30 s c t 2 0 2 2 – 0 1 – 0 4 T 0 0
    3A 35 36 3A 32 31 5A 62 6E 6D 78 1C 46 61 6B 65 : 5 6 : 2 1 Z b n m x . F a k e
    20 74 65 73 74 20 65 68 65 61 6C 74 68 2E 76 79 t e s t e h e a l t h . v y
    6E 63 6B 65 2E 6F 72 67 62 69 73 78 2B 4D 69 6E n c k e . o r g b i s x + M i n
    69 73 74 72 79 20 6F 66 20 4D 61 67 69 63 20 48 i s t r y o f M a g i c H
    65 61 6C 74 68 2C 20 57 68 69 74 65 68 61 6C 6C e a l t h , W h i t e h a l l
    2C 20 4C 6F 6E 64 6F 6E 62 63 69 78 1C 55 52 4E , L o n d o n b c i x . U R N
    3A 55 56 43 49 3A 30 31 3A 49 45 3A 37 37 5A 37 : U V C I : 0 1 : I E : 7 7 Z 7
    35 5A 32 34 34 5A 31 33 35 58 40 44 75 6D 6D 79 5 Z 2 4 4 Z 1 3 5 X @ D u m m y
    53 69 67 6E 61 74 75 72 65 21 21 44 75 6D 6D 79 S i g n a t u r e ! ! D u m m y
    53 69 67 6E 61 74 75 72 65 21 21 44 75 6D 6D 79 S i g n a t u r e ! ! D u m m y
    53 69 67 6E 61 74 75 72 65 21 21 44 75 6D 6D 79 S i g n a t u r e ! ! D u m m y
    53 69 67 6E 61 74 75 72 65 21 21 S i g n a t u r e ! !

    Message is now compressed with ZLIB and the compression was useful as it decreased the size from 411 to 326 bytes
    78 DA 95 8D 4D 4E C2 40 00 85 6D 24 1E 82 B0 18 x . . . M N . @ . . m $ . . . .
    36 A2 91 36 33 ED B4 94 AE 14 C4 40 42 89 11 F1 6 . . 6 3 . . . . . . @ B . . .
    A7 BB 99 61 DA 8E 94 99 A4 1D 48 59 9A 78 10 35 . . . a . . . . . . H Y . x . 5
    6C BC 80 07 F1 00 7A 15 0B F1 02 BE B7 7A 2F 5F l . . . . . z . . . . . . z / _
    DE FB 7A 09 DF 8D E3 DA F0 E0 4F AF 8F C6 60 6B . . z . . . . . . . O . . . ` k
    D0 D1 A0 56 27 3F E5 E7 51 9D 7C B7 BC AE 71 F8 . . . V ‘ ? . . Q . | . . . q .
    66 6C D9 9A E7 1C 59 8E 05 D9 5C D1 27 E4 FB BE f l . . . . Y . . . \ . ‘ . . .
    09 6D 13 39 4C 92 E5 96 C6 32 E9 11 59 B9 60 B1 . m . 9 L . . . . 2 . . Y . ` .
    D4 49 EF 62 52 79 4A 13 C9 C3 7D 99 48 CD C3 5D . I . b R y J . . . } . H . . ]
    45 F4 F3 07 65 AA FA A0 3A 11 3E 86 AE D3 85 D0 E . . . e . . . : . > . . . . .
    A3 5A A7 E3 6B 0F 7B D8 C4 54 E7 C2 F6 20 46 2E . Z . . k . { . . T . . . F .
    84 90 6A 56 B6 26 44 0B 25 49 06 FA 6A 2D E6 80 . . j V . & D . % I . . j – . .
    F1 6A 2D 07 27 31 59 70 10 AB 1C 10 A0 79 A1 4F . j – . ‘ 1 Y p . . . . . y . O
    69 C1 B4 0D 6D DB 84 C8 84 F8 16 C2 C0 F5 02 1B i . . . m . . . . . . . . . . .
    45 54 2E CB C6 D5 0E DE 61 80 A7 9C 64 3A B5 D6 E T . . . . . . a . . . d : . .
    1B C9 16 DC 52 79 42 45 51 9E 85 42 8A 42 E7 1B . . . . R y B E Q . . B . B . .
    A0 62 10 92 44 30 30 DC 73 6D 70 9F 0A CD 53 92 . b . . D 0 0 . s m p . . . S .
    65 6D 30 56 72 AE 24 65 A2 6C CC 6E 26 C1 EC AE e m 0 V r . $ e . l . n & . . .
    3F 0A 20 0A 46 83 A0 D3 89 3A 6E 64 63 1C 21 C7 ? . . F . . . . : n d c . ! .
    7D 38 BF 5C 2D 97 9B A9 48 24 D1 AB 9C 37 9B FF } 8 . \ – . . . H $ . . . 7 . .
    CD BF 81 44 87 25 . . . D . %

    Prepending “HC1:” and base45 encoding:
    b’HC1:NCFZ+IZY93PO/20+ZDP%3ZBM +64GIIP6D$ME0MK ONI83C299LPHJFSRGZISXKQ69YNJ922QXDF8G1LUNJF.M1RF0Z8NM:5N8SBJFYBSA SEKUE3A-7IN8C.HQ6CKC/4+2TDEAMYFH%NGIEU.C-MRY9TLEBCX0

Leave a Comment

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