Skip to content

Instantly share code, notes, and snippets.

@zougloub
Last active April 6, 2017 22:11
Show Gist options
  • Save zougloub/0e7cd7dee16b1ee182dcf465a3b6fac4 to your computer and use it in GitHub Desktop.
Save zougloub/0e7cd7dee16b1ee182dcf465a3b6fac4 to your computer and use it in GitHub Desktop.
QR code abuse: when the code is decoded by a computer, we can pervert it by adding moar extra data in chroma...

QR Code Abuse

qrcode-luma.png

QR code with additional data in grey level.

qrcode-chroma.png

QR code with additional data in chroma plane.

The problem: you want to use a QR code to convey base information, but want to pass on additional, optional data, that can be reasonably well decoded when the QR code image is transported.

Proposed solutions: use more QR codes, overlaid with the base one. One method is to use chroma planes to transport more data: when decoding from YUV, the Y would not be altered, and the U resp. V planes would contain their own QR codes. Another method is to add layers of QR codes at discrete grey levels, in a way that the value of a pixel is unambiguously the sum of values from different layers.

#!/usr/bin/env python
# -*- coding: utf-8 vi:noet
# What if we made crazy QR codes with extra data in chroma...
"""
To reduce flicker, the extra data can be gray-encoded
"""
import sys, struct, ctypes
import cv2
import numpy as np
def gray_enc(value):
return (value^(value>>1))
def gray_dec(value, bits):
out = 0
for idx_bit in range(bits-1, -1, -1):
out |= (((value >> idx_bit) & 1) ^ ((out >> (idx_bit+1)) & 1)) << idx_bit
return out
def sbits(value, bits):
return bin(value | (1<<bits))[3:]
bits = 8
for i in range(1<<bits):
e = gray_enc(i)
d = gray_dec(e, bits)
assert d == i
continue
print("%s %s %s" % (
sbits(i, bits),
sbits(e, bits),
sbits(d, bits),
))
def enc_flt(x):
b = struct.pack("<f", float(x))
i = struct.unpack("<I", b)[0]
g = gray_enc(i)
return struct.pack("<I", g)
class QREncoder(object):
def __init__(self):
self._lib = ctypes.CDLL("libqrencode.so")
self._lib.QRcode_encodeData.argtypes = ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_int
class QRCode(ctypes.Structure):
_fields_ = [
("version", ctypes.c_int),
("width", ctypes.c_int),
("data", ctypes.POINTER(ctypes.c_uint8)),
]
self._lib.QRcode_encodeData.restype = ctypes.POINTER(QRCode)
def encode(self, value):
assert isinstance(value, bytes)
version = 2
level = 3
qrcode = self._lib.QRcode_encodeData(len(value), value, version, level)
res = qrcode.contents
version = int(res.version)
size = int(res.width)
data = bytearray(size*size)
for idx_data in range(size*size):
data[idx_data] = (1 - (res.data[idx_data] & 1)) * 255
data = bytes(data)
self._lib.QRcode_free(qrcode)
return version, size, data
def make_samesize(datas):
# make the QR codes the same size (somehow)
sizes = [None] * len(datas)
payloads = [None] * len(datas)
encoder = QREncoder()
while sizes[0] is None or not reduce(lambda x, y: x and y, map(lambda x: x == sizes[0], sizes)):
if sizes[0] is not None:
for idx_data, data in enumerate(datas):
if sizes[idx_data] < max(sizes):
datas[idx_data] += b" "
for idx_data, data in enumerate(datas):
version, size, data = encoder.encode(data)
payloads[idx_data] = data
sizes[idx_data] = size
return size, payloads
def example_encode_luma():
datas = []
datas.append("http://zougloub.eu")
datas.append("hello world!")
datas.append("hello again!")
datas.append("hello ter!")
size, payloads = make_samesize(datas)
scale = 16
img = np.zeros((size+2, size+2), dtype=np.uint8) + 255
layer_weights = 32, 16, 8, 4, 2
assert len(datas) <= len(layer_weights)
img_upd = img[1:-1,1:-1]
img_qr0 = np.fromstring(payloads[0], dtype=np.uint8).reshape((size, size))
img_upd[:] = img_qr0
for idx_data, data in enumerate(payloads[1:]):
img_qr = np.int32(np.fromstring(data, dtype=np.uint8).reshape((size, size)))
weight = layer_weights[idx_data]
comp = img_qr0 != img_qr
compimg = np.ones_like(img_upd)
compimg[comp] = weight
cv2.imwrite("comp-%d.png" % idx_data, compimg)
img_upd[img_upd >= 128] -= compimg[img_upd >= 128]
img_upd[img_upd < 128] += compimg[img_upd < 128]
cv2.imwrite("upd-%d.png" % idx_data, img)
img = cv2.resize(img, ((size+2)*scale, (size+2)*scale), interpolation=cv2.INTER_NEAREST)
cv2.imwrite("qrcode-luma.png", img)
def example_encode_chroma():
datas = []
datas.append("http://zougloub.eu")
# some structure...
extradata = {"x": 1.33}
# more data with some string encoding
datas.append("f" + enc_flt(1.33))
# more data as a string
datas.append("hello world!")
assert len(datas) == 3, "Unimplemented"
size, payloads = make_samesize(datas)
scale = 16
img = np.zeros((size+2, size+2), dtype=np.uint8) + 255
img_upd = img[1:-1,1:-1]
img_qr0 = np.fromstring(payloads[0], dtype=np.uint8).reshape((size, size))
img_upd[:] = img_qr0
img = cv2.merge((img, img, img))
img_yuv = cv2.cvtColor(img, cv2.COLOR_RGB2YUV)
for idx_plane in range(1, 3):
info = payloads[idx_plane]
img_qr = np.fromstring(info, dtype=np.dtype('B')).reshape((size, size))
img_upd = img_yuv[1:-1,1:-1,idx_plane]
img_upd[img_qr0 != img_qr] -= 80
img = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2RGB)
img = cv2.resize(img, ((size+2)*scale, (size+2)*scale), interpolation=cv2.INTER_NEAREST)
cv2.imwrite("qrcode-chroma.png", img)
if __name__ == '__main__':
example_encode_chroma()
example_encode_luma()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment