Skip to content

Instantly share code, notes, and snippets.

@yaci
Last active February 18, 2022 10:16
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save yaci/96b53642bacfe440949cbc781bb479b1 to your computer and use it in GitHub Desktop.
Save yaci/96b53642bacfe440949cbc781bb479b1 to your computer and use it in GitHub Desktop.
Decode information from European covid19 travel certificates
#######
#
# REQUIRES: pip install cose #!!!!
#
# HOW TO USE:
# 1. Use your favourite QR scanner to scan the code from a certificate,
# you'll get a string that starts with "HC1:"
# 2. Run the script: python3 covid-qr-certificate-decoder.py
# 3. Paste the string when prompted
#
#
# READING (how to interpret the output and test data):
# api spec: https://github.com/eu-digital-green-certificates/dgca-issuance-web/blob/main/src/generated-files/dgc-combined-schema.d.ts
# https://github.com/eu-digital-green-certificates/dgca-issuance-web/blob/main/src/misc/edgcProcessor.tsx
# https://github.com/admin-ch/CovidCertificate-Examples
# https://github.com/eu-digital-green-certificates/dgc-testdata/tree/main/PL
#
#######
from typing import Union
import zlib
from cose.messages import CoseMessage
import cbor2
import pprint
import json
# example code for testing
# QRCODE = 'HC1:6BFC80Z80T9WTWGVLK659A:TG4G$BTZM0X*4FBBE*3FN0KKC+H3WY0/9CWC4*70 6AD97TK0F90KECTHGXJC$+D.JCBECB1A-:8$966469L6OF6VX6Z/ER2DD46JH8946XJCCWENF6OF63W5NW60A6WJCT3E 6AWJC0FDTA6AIA%G7X+AQB9746XG7TS9 967:6DL6WX8BM8CA6DB83R63X6$A7UF6QG8Q46/A8.A8$96V47.JCP9EJY8L/5M/5 96.96WF6%JCXQEIN8G/D6LE ZDQZCAJB0LEE4F0ECKQEPD09WEQDD+Q6TW6FA7C46TPCBEC8ZKW.CHWEBIAG09:S9Q+9DN90S7BIAOT9W.CUEEY3EAECWGDMXG2QDUW5*MEWUMMPCG/D8EDETAG+9*NAWB8 JC6/DYOACEC+EDR/OLEC$PC5$CUZCY$5Y$5JPCT3E5JDKA7Q47%964W5WA6N68$E5FAWIBG9SQCLEH19FZMD8B TL6AW8JJRBHTB91L0GMSNRCBBPL8R958CR2:7T84H7*6C V86W0*8G1MZJVS72L:M5WAB9UB0HF0'
# credits: https://github.com/kirei/python-base45/blob/main/base45/__init__.py
def b45decode(s: Union[bytes, str]) -> bytes:
"""Decode base45-encoded string to bytes"""
BASE45_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
res = []
try:
if isinstance(s, str):
buf = [BASE45_CHARSET.index(c) for c in s]
else:
buf = [BASE45_CHARSET.index(c) for c in s.decode()]
buflen = len(buf)
for i in range(0, buflen, 3):
x = buf[i] + buf[i + 1] * 45
if buflen - i >= 3:
x = buf[i] + buf[i + 1] * 45 + buf[i + 2] * 45 * 45
res.extend(list(divmod(x, 256)))
else:
res.append(x)
return bytes(res)
except (ValueError, IndexError, AttributeError):
raise ValueError("Invalid base45 string")
def main() -> None:
data = input("Paste the decoded QR code string and press Enter:\n")
if data.startswith('HC1:'): data = data[4:]
decoded = b45decode(data)
decompressed = zlib.decompress(decoded);
msg = CoseMessage.decode(decompressed);
phdr, uhdr = msg._hdr_repr()
payload = cbor2.loads(msg.payload)
#payload is the most interesting part, the others are rather boring
print("\n")
print("==PHDR==\n")
pprint.pprint(phdr)
print("==UHDR==\n")
pprint.pprint(uhdr)
#print("==SIGNATURE==\n",msg.signature,"\n")
print("==PAYLOAD==")
print(json.dumps(payload, ensure_ascii=False, indent=4))
print("\n")
if __name__ == "__main__":
main()
@matze-anyline
Copy link

Hey thank you for this snippet! Do you know where and how I could verify the signature?

@yaci
Copy link
Author

yaci commented Jun 21, 2021

@matze-anyline Check out the official, example implementations of mobile apps:

Can't tell if they are working (I had troubles with running them) but reading the code can give you an idea how to implement a verification procedure.

@Alex3nder
Copy link

Possible to reverse the process and create new QR code based on new informations ?

@TimoA200
Copy link

Possible to reverse the process and create new QR code based on new informations ?

Have you found a method?

@Mikel547
Copy link

Possible to reverse the process and create new QR code based on new informations ?

Any findings so far?

@TimoA200
Copy link

Problem is that the information is hashed and signed via a private key, wich only certain authorities have access to.
The check apps then check this signing via a public key.
So yes it's relatively easy to create a new QR code based on new information, but you won't get a valid vaccination status in any check app.

@UBT-FJ
Copy link

UBT-FJ commented Oct 20, 2021

Strange, the script works if I run it in the same file, but if I pass the data as string from another script it throws an error:

pi@raspberrypi:~/app $ python3 123.py
Traceback (most recent call last):
File "/home/pi/app/decoder.py", line 38, in b45decode
buf = [BASE45_CHARSET.index(c) for c in s]
File "/home/pi/app/decoder.py", line 38, in
buf = [BASE45_CHARSET.index(c) for c in s]
ValueError: substring not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "123.py", line 27, in
decoder.main(data)
File "/home/pi/app/decoder.py", line 57, in main
decoded = b45decode(data)
File "/home/pi/app/decoder.py", line 51, in b45decode
raise ValueError("Invalid base45 string")
ValueError: Invalid base45 string

@terminator-engine
Copy link

Any idea about how to find and break this private key in order to have a valid signature?

@TimoA200
Copy link

TimoA200 commented Nov 2, 2021

Any idea about how to find and break this private key in order to have a valid signature?

Nothing found yet

@terminator-engine
Copy link

Found this : https://www.zofrex.com/blog/2020/10/20/alg-none-jwt-nhs-contact-tracing-app/
Unfortunately it seems this is already settled.

@TimoA200
Copy link

TimoA200 commented Nov 9, 2021

Found this : https://www.zofrex.com/blog/2020/10/20/alg-none-jwt-nhs-contact-tracing-app/ Unfortunately it seems this is already settled.

Good finding

@krajcit
Copy link

krajcit commented Nov 24, 2021

greetings gentlemen. I confirm, that the reverse process of creating a new qr code (based on the new input information) does work nicely, however the issue with the verification makes the qr code invalid in the app. I hope a clever solution will come up.

@terminator-engine
Copy link

That's pretty normal because all the datas are hashed with a primary key that we have no idea about...
Every primary key is unique and directly registered to the national database. So instead of directly hacking this database, I'm not sure that there is so many other solutions... We can consider that this system is inviolable.

@DavidBor17
Copy link

@fjusufi-ubt it happened to you, so it might help you: When I copied the qrcode, it had two "HC1:", so if you divide it into two different qrcodes may work for you, it did in my case. I hope it helps

@Mike80p
Copy link

Mike80p commented Dec 10, 2021

@DavidBor17 could you help me to make a valid GP?

@Apo93160
Copy link

@krajcit Hey sir, can you provide me your source code allowing to put new input information in the qr code pls ?
I'm trying to create one since 2 days but without sucess...

@poligraph2012
Copy link

@krajcit Hello! Please send me your source code to qreate QR with new input information? It's very important for me and it's for me only)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment