Search code examples
pythonqr-codedigital-signaturezbar

why is this signature different from the original after creating a qr code and scanning it with pyzbar?


I'm generating a digital signature(using https://pypi.org/project/rsa/) and saving it on a qrcode(using https://pypi.org/project/qrcode/) so I can distribute them on paper and scan them later. However, the byte sequence scanned is different from the one i originally created, why the verification fails.

The following code

from qrcode import make
from io import BytesIO
import rsa
from pyzbar.pyzbar import decode
from PIL import Image

#loading the private key
with open("privkey.pem", "rb") as f:
        private_key = rsa.PrivateKey.load_pkcs1(f.read())

#first, I sign some data and create a qrcode of it
signature_data = bytes("test", "utf-8")
print(signature_data)
signature = rsa.sign(signature_data, private_key, "SHA-512")
barcode_data = signature_data + b"endm" + signature
print(barcode_data)
image = make(barcode_data)
image.save("test.jpeg",format="jpeg")

#Then, I read the image and scan it
scannedimage = Image.open("test.jpeg")
read = decode(scannedimage)[0][0]
print(read)
print(read==barcode_data)

outputs

b'test'
b"testendm$.\x02s\xbf%\x11\xda=m\xbe\xd6j\xc8\x8f\x1a\xb3z2D\xb4\xc6\xc0)e\xfa\x908\xac7\xa0Y\xd3C\xb7\xd0\xca\xea\xf5\x9c0\xab\xe7?\xe2\x8e\x15mr\x90lXuQ\x14\x82\xa4\x07\xa6+\x0e\xa4S\xb99~\xae\xbb\x1f\xaa\xe0\x1f\x87\xa0\xa7\x17`\xc3L\x94\xe7ug&\x1dVo\xa0\x17e+\x166W\x87j\xebs\xac\x0eR\x95\x884\t\xba\xfd\xae M@x\xd7\xe7'v\xb0\x07m,l\xc8\x04\x11\xd3q/\xee\xe5\x12\x85\xcd\x82\x99XR\xe6\x9fd\xef\xb6\x11u\xc2\x9eq\x07\x0f\x12\x06Hkl\x98\x1c\x98\x0f\xe0\xad\x16\x08,d\x9bYA\xe5@\x9dy=\xadr\x03\x11I\t\xcc9\xa4\xec\x10\xf3\x04N\xbfEmF\xed8V\xaa\xb0\x84\x81\xc6\xf52\x1d\xeb\xdf\xeb\xd1e\x8a\xd9\xad\x0e\x9bCS_\xc4\x1c|\x86\x0bv\xce\xaff\xe1\xa7\xfeO\xf6\xdc]\x14\xed; \x9aMX}\xe5bme1\xf7\xa0^\xa5\x8f-\x9c\x00\xa5\xd4R\x01J\t"
b"testendm$.\x02s\xc2\xbf%\x11\xc3\x9a=m\xc2\xbe\xc3\x96j\xc3\x88\xc2\x8f\x1a\xc2\xb3z2D\xc2\xb4\xc3\x86\xc3\x80)e\xc3\xba\xc2\x908\xc2\xac7\xc2\xa0Y\xc3\x93C\xc2\xb7\xc3\x90\xc3\x8a\xc3\xaa\xc3\xb5\xc2\x9c0\xc2\xab\xc3\xa7?\xc3\xa2\xc2\x8e\x15mr\xc2\x90lXuQ\x14\xc2\x82\xc2\xa4\x07\xc2\xa6+\x0e\xc2\xa4S\xc2\xb99~\xc2\xae\xc2\xbb\x1f\xc2\xaa\xc3\xa0\x1f\xc2\x87\xc2\xa0\xc2\xa7\x17`\xc3\x83L\xc2\x94\xc3\xa7ug&\x1dVo\xc2\xa0\x17e+\x166W\xc2\x87j\xc3\xabs\xc2\xac\x0eR\xc2\x95\xc2\x884\t\xc2\xba\xc3\xbd\xc2\xae M@x\xc3\x97\xc3\xa7'v\xc2\xb0\x07m,l\xc3\x88\x04\x11\xc3\x93q/\xc3\xae\xc3\xa5\x12\xc2\x85\xc3\x8d\xc2\x82\xc2\x99XR\xc3\xa6\xc2\x9fd\xc3\xaf\xc2\xb6\x11u\xc3\x82\xc2\x9eq\x07\x0f\x12\x06Hkl\xc2\x98\x1c\xc2\x98\x0f\xc3\xa0\xc2\xad\x16\x08,d\xc2\x9bYA\xc3\xa5@\xc2\x9dy=\xc2\xadr\x03\x11I\t\xc3\x8c9\xc2\xa4\xc3\xac\x10\xc3\xb3\x04N\xc2\xbfEmF\xc3\xad8V\xc2\xaa\xc2\xb0\xc2\x84\xc2\x81\xc3\x86\xc3\xb52\x1d\xc3\xab\xc3\x9f\xc3\xab\xc3\x91e\xc2\x8a\xc3\x99\xc2\xad\x0e\xc2\x9bCS_\xc3\x84\x1c|\xc2\x86\x0bv\xc3\x8e\xc2\xaff\xc3\xa1\xc2\xa7\xc3\xbeO\xc3\xb6\xc3\x9c]\x14\xc3\xad; \xc2\x9aMX}\xc3\xa5bme1\xc3\xb7\xc2\xa0^\xc2\xa5\xc2\x8f-\xc2\x9c"
False

Why are the two bytestrings different? Is there a conversion I don't know about in one module I use?


Solution

    1. I would highly recommend that you encode whatever you want to sign in base64 before signing. This is a standard approach for digital signatures, ensuring consistency of the signed data.
    2. The output of the pyzbar.decode function is an array of "Decoded" objects, of which you're not retrieving the correct element.

    Below the corrected code:

    from qrcode import make
    from io import BytesIO
    import rsa
    from pyzbar.pyzbar import decode
    from PIL import Image
    from base64 import b64encode, b64decode
    
    #loading the private key
    with open("privkey.pem", "rb") as f:
            private_key = rsa.PrivateKey.load_pkcs1(f.read())
    
    #first, I sign some data and create a qrcode of it
    signature_data = bytes("test", "utf-8")
    # Ideally, you'd want to base64 encode signature_data
    print(signature_data)
    signature = rsa.sign(signature_data, private_key, "SHA-512")
    barcode_data = b64encode(signature_data + b"endm" + signature)
    print(barcode_data)
    image = make(barcode_data)
    image.save("test.jpeg",format="jpeg")
    
    #Then, I read the image and scan it
    scannedimage = Image.open("test.jpeg")
    read = decode(scannedimage)[0].data
    print(read)
    print(read==barcode_data)