The EU Green Pass uses a Base-45 format for the QR code. This page will generate a signed object for the QR on vaccination status, and then decode it.
Vaccine Passport |
Outline
An overview of the processing is defined in [1] as:
First we start with the basic data for the vaccine status report:
{ "1": "GB", "4": 1992212161, "6": 1425632973, "-260": { "1": { "v": [ { "ci": "URN:UVCI:01:GB:D23AD1B216A11133B272BEA32C426AC0#D", "co": "GB", "dn": 1, "dt": "2021-01-14", "is": "NHS Anywhere", "ma": "ORG-110302699", "mp": "EU/1/21/1529", "sd": 2, "tg": "341534114", "vp": "3339315014" } ], "dob": "1977-01-10", "nam": { "fn": "SMITH", "gn": "FRED SMITH", "fnt": "SMITH", "gnt": "FRED SMITH" }, "ver": "1.3.0" } }
This contains the date of birth of the person, their name, and the details of their vaccination. To then encode this into the QR, we create a signed object. In this following case this is an EdDSA signature signed by a private key (such as from the NHS). The library used is COSE:
message='{"1": "GB","4": 1772210913,"6": 1830374921,"-260": { "1": {"v": [ {\"ci": \"URN:UVCI:01:GB:D36AF2C745B94400A176CF3E3B526BA#F","co": "GB","dn": 1,"dt": "2021-04-11","is": "NHS","ma": "ORG-100331689","mp": "EU/1/21/1529","sd": 2,"tg": "840539006","vp": "1119305005"}],"dob": "1977-01-04","nam": {"fn": "SMITH","gn": "FRED SMITH","fnt": "SMITH","gnt": "FRED SMITH"},"ver": "1.3.0"}}}'
The code is:
import zlib import base45 from cose.messages import CoseMessage from binascii import unhexlify, hexlify from cose.messages import Sign1Message from cose.keys import CoseKey from cose.headers import Algorithm, KID from cose.algorithms import EdDSA from cose.keys.curves import Ed25519 from cose.keys.keyparam import KpKty, OKPKpD, OKPKpX, KpKeyOps, OKPKpCurve from cose.keys.keytype import KtyOKP from cose.keys.keyops import SignOp, VerifyOp msg = Sign1Message(phdr = {Algorithm: EdDSA, KID: b'kid2'},payload = message.encode()) cose_key = {KpKty: KtyOKP,OKPKpCurve: Ed25519,KpKeyOps: [SignOp, VerifyOp],OKPKpD: unhexlify(b'9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'),OKPKpX: unhexlify(b'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a')} cose_key = CoseKey.from_dict(cose_key) msg.key = cose_key en = msg.encode(tag=True,sign=True)
After this we compress with zlib, and then encode with Base-45:
compressed = zlib.compress(en) encoded = base45.b45encode(compressed) print ("Code: ",encoded.decode())
This then gives us the code for the QR code. In this case it is:
6BFX A0087I0ZQ2M95*7DF.4 4JUZ1U0OC0OL-5RO3E9VR/BVBKBPD*62: D*OH+$2$XEU-RI7A18PFNH-:DVN7 VJWBKB%JHBWN.D0-D2$4NV23QIH6O+P9N*SR:4*O3+:TH%D4R9H7D-%MIQ4OWDNTC6D261HBHQ445L52GG48TLY4QDEBYAK194L/STCA7M1KR61P8DSHW069.QN-G3 MHBJV.CI588 STC55J22Z9M2H%41R10REKDRI M9XAP8%ROZHKIA6*L5JNDU2JUII5LNV0MF7PI7RR35WT Y9E*SE:NV%LKHJ *8I40SDIA+G2X6EXQ.53/65.GQTG13M9%OQZPU .QS/DPIPG30$RIR226V8GCO566H7OK0PM+IIG15QQWKP9FM3ZN0XFIY9+2I09LXSMKOIE/0KSJ29LV%5LUL4BW:+B-J6RWPIEDL.RPO0CZ2CVSQAWIIVAETH3T 49E3YOREXJ.0P7FV5Z88DWXKUJWVRGD2T36MB5RAHYUQUL712W6S3Y9Z-TC4VY5SYTBGS361S6XR%AB.WVVFS++F
We then add "HC1:" to the string, and this is then simply written as a QR code. To decode we read the QR code data, and then decode the Base-45, decompress, and check the signature:
decoded = base45.b45decode(encoded.decode()) # decompress using zlib decompressed = zlib.decompress(decoded) cos = CoseMessage.decode(decompressed) cos.key = cose_key print ("Signature: ",cos.verify_signature()) print (cos.payload.decode())
Coding
To run the code, you need to pip install cose:
References
[1] Corici, A. A., Hühnlein, T., Hühnlein, D., & Rode, O. (2021, August). Towards Interoperable Vaccination Certificate Services. In The 16th International Conference on Availability, Reliability and Security (pp. 1-9) [here].