Extract EU Digital COVID Certificate QR code data from images and PDFs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import json | |
import pathlib | |
import sys | |
import zlib | |
import cbor2 | |
from base45 import b45decode | |
from cose.messages import CoseMessage | |
from PIL import Image | |
from pygments import formatters, highlight, lexers | |
from pyzbar import pyzbar | |
from pyzbar.wrapper import ZBarSymbol | |
__doc__ = """Extracts EU Digital COVID Certificate QR code data from images and PDFs""" | |
def detect_dcc_qr_codes(image): | |
"""Extracts EU Digital COVID Certificate QR codes from an image""" | |
qr_codes = pyzbar.decode(image, symbols=[ZBarSymbol.QRCODE]) | |
decoded = [qr.data.decode("utf-8") for qr in qr_codes] | |
return [qr for qr in decoded if qr.startswith("HC1:")] | |
def parse_digital_green_cert(qr_code_data): | |
"""Extracts the payload from an EU Digital COVID Certificate QR code""" | |
base45_data = qr_code_data[len("HC1:") :] | |
compressed_data = b45decode(base45_data) | |
cose_signed_document = zlib.decompress(compressed_data) | |
cose_message = CoseMessage.decode(cose_signed_document) | |
payload = cbor2.loads(cose_message.payload) | |
return payload | |
def highlight_json(obj): | |
"""Prints an object as syntax-highlighted JSON""" | |
formatted_json = json.dumps(obj, indent=2) | |
return highlight(formatted_json, lexers.JsonLexer(), formatters.TerminalFormatter()) | |
def main(arguments): | |
parser = argparse.ArgumentParser( | |
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter | |
) | |
parser.add_argument("infile", help="QR code image or PDF", type=pathlib.Path) | |
args = parser.parse_args(arguments) | |
# PIL can't read PDFs, so render these first | |
if args.infile.suffix == ".pdf": | |
import pdf2image | |
images = pdf2image.convert_from_path(args.infile) | |
else: | |
images = [Image.open(args.infile)] | |
# Detect all DCC QR codes | |
qr_codes = [] | |
for image in images: | |
qr_codes.extend(detect_dcc_qr_codes(image)) | |
# Print formatted results to the terminal | |
for qr_code in qr_codes: | |
payload = parse_digital_green_cert(qr_code) | |
print(highlight_json(payload)) | |
return 0 | |
if __name__ == "__main__": | |
sys.exit(main(sys.argv[1:])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment