pyaota.util.qrcrypto module

qrcrypto.py

QR code image generation and lightweight answer encryption for pyaota.

Encryption uses an HMAC-SHA256-based stream cipher (no external dependencies):
  • Keystream derived from HMAC(key, block_counter) — CTR-mode equivalent

  • 8-byte authentication tag appended to the ciphertext

  • Standard base64 encoding (LaTeX-safe: no underscores)

QR payload format: <version_label>:<base64(ciphertext + 8-byte-mac)>

pyaota.util.qrcrypto.decrypt_answers(key_hex, payload)[source]

Decrypt a base64 payload produced by encrypt_answers.

Returns a list of single-character answer strings. Raises ValueError if the payload is malformed or the MAC check fails.

Return type:

list

pyaota.util.qrcrypto.encrypt_answers(key_hex, answers)[source]

Encrypt a list of single-character answer strings and return a URL-safe base64 payload suitable for embedding in a QR code.

Answers must already be normalised to single characters (True/False → a/b, MCQ choices lower-cased).

Return type:

str

pyaota.util.qrcrypto.generate_key()[source]

Return a new random 32-byte key as a lowercase hex string.

Return type:

str

pyaota.util.qrcrypto.generate_qr_png(content, output_path, error_correction='M', display_size_cm=None, scan_dpi=300)[source]

Generate a QR code PNG for content and write it to output_path.

Return type:

None

error_correction selects the Reed-Solomon level:

‘L’ = ~7 %, ‘M’ = ~15 % (default), ‘Q’ = ~25 %, ‘H’ = ~30 %

After writing the image is decoded with OpenCV at full resolution. If display_size_cm is given, a second verification is performed by downsampling to the expected scan resolution (display_size_cm × scan_dpi / 2.54 pixels square) so that readability at print size is confirmed before the exam PDF is built.

Requires the qrcode[pil] package (Pillow is already a dependency).