QR functionality (sorta).

This commit is contained in:
Aaron D. Lee
2025-12-28 12:04:15 -05:00
parent 5bd49cb581
commit 653de8cbaa
17 changed files with 1677 additions and 599 deletions

View File

@@ -30,6 +30,20 @@ from stegasoo import (
FilePayload,
)
# QR Code utilities
try:
from stegasoo.qr_utils import (
extract_key_from_qr_file,
generate_qr_code,
has_qr_read, has_qr_write,
can_fit_in_qr, needs_compression,
)
HAS_QR = True
except ImportError:
HAS_QR = False
has_qr_read = lambda: False
has_qr_write = lambda: False
# ============================================================================
# CLI SETUP
@@ -177,34 +191,36 @@ def generate(pin, rsa, pin_length, rsa_bits, words, output, password, as_json):
@click.option('--embed-file', '-e', type=click.Path(exists=True), help='Embed a file (binary)')
@click.option('--phrase', '-p', required=True, help='Day phrase')
@click.option('--pin', help='Static PIN')
@click.option('--key', '-k', type=click.Path(exists=True), help='RSA key file')
@click.option('--key-password', help='RSA key password')
@click.option('--key', '-k', type=click.Path(exists=True), help='RSA key file (.pem)')
@click.option('--key-qr', type=click.Path(exists=True), help='RSA key from QR code image')
@click.option('--key-password', help='RSA key password (for encrypted .pem files)')
@click.option('--output', '-o', type=click.Path(), help='Output file (default: auto-generated)')
@click.option('--date', 'date_str', help='Date override (YYYY-MM-DD)')
@click.option('--quiet', '-q', is_flag=True, help='Suppress output except errors')
def encode_cmd(ref, carrier, message, message_file, embed_file, phrase, pin, key, key_password, output, date_str, quiet):
def encode_cmd(ref, carrier, message, message_file, embed_file, phrase, pin, key, key_qr, key_password, output, date_str, quiet):
"""
Encode a secret message or file into an image.
Requires a reference photo, carrier image, and day phrase.
Must provide either --pin or --key (or both).
Must provide either --pin or --key/--key-qr (or both).
For text messages, use -m or -f or pipe via stdin.
For binary files, use -e/--embed-file.
RSA key can be provided as a .pem file (--key) or QR code image (--key-qr).
\b
Examples:
# Text message
# Text message with PIN
stegasoo encode -r photo.jpg -c meme.png -p "apple forest thunder" --pin 123456 -m "secret"
# Text from file
stegasoo encode -r photo.jpg -c meme.png -p "words" --pin 123456 -f message.txt
# With RSA key file
stegasoo encode -r photo.jpg -c meme.png -p "words" -k mykey.pem -m "secret"
# Embed a binary file (PDF, ZIP, etc.)
# With RSA key from QR code image
stegasoo encode -r photo.jpg -c meme.png -p "words" --key-qr keyqr.png -m "secret"
# Embed a binary file
stegasoo encode -r photo.jpg -c meme.png -p "words" --pin 123456 -e secret.pdf
# Pipe text
echo "secret" | stegasoo encode -r photo.jpg -c meme.png -p "words" --pin 123456
"""
# Determine what to encode
payload = None
@@ -223,14 +239,35 @@ def encode_cmd(ref, carrier, message, message_file, embed_file, phrase, pin, key
else:
raise click.UsageError("Must provide message via -m, -f, -e, or stdin")
# Load key if provided
# Load key if provided (from .pem file or QR code image)
rsa_key_data = None
rsa_key_from_qr = False
if key and key_qr:
raise click.UsageError("Cannot use both --key and --key-qr. Choose one.")
if key:
rsa_key_data = Path(key).read_bytes()
elif key_qr:
if not HAS_QR or not has_qr_read():
raise click.ClickException(
"QR code reading not available. Install: pip install pyzbar\n"
"Also requires system library: sudo apt-get install libzbar0"
)
key_pem = extract_key_from_qr_file(key_qr)
if not key_pem:
raise click.ClickException(f"Could not extract RSA key from QR code: {key_qr}")
rsa_key_data = key_pem.encode('utf-8')
rsa_key_from_qr = True
if not quiet:
click.echo(f"Loaded RSA key from QR code: {key_qr}")
# QR code keys are never password-protected
effective_key_password = None if rsa_key_from_qr else key_password
# Validate security factors
if not pin and not rsa_key_data:
raise click.UsageError("Must provide --pin or --key (or both)")
raise click.UsageError("Must provide --pin or --key/--key-qr (or both)")
try:
ref_photo = Path(ref).read_bytes()
@@ -243,7 +280,7 @@ def encode_cmd(ref, carrier, message, message_file, embed_file, phrase, pin, key
day_phrase=phrase,
pin=pin or "",
rsa_key_data=rsa_key_data,
rsa_password=key_password,
rsa_password=effective_key_password,
date_str=date_str,
)
@@ -278,37 +315,63 @@ def encode_cmd(ref, carrier, message, message_file, embed_file, phrase, pin, key
@click.option('--stego', '-s', required=True, type=click.Path(exists=True), help='Stego image')
@click.option('--phrase', '-p', required=True, help='Day phrase')
@click.option('--pin', help='Static PIN')
@click.option('--key', '-k', type=click.Path(exists=True), help='RSA key file')
@click.option('--key-password', help='RSA key password')
@click.option('--key', '-k', type=click.Path(exists=True), help='RSA key file (.pem)')
@click.option('--key-qr', type=click.Path(exists=True), help='RSA key from QR code image')
@click.option('--key-password', help='RSA key password (for encrypted .pem files)')
@click.option('--output', '-o', type=click.Path(), help='Save decoded content to file')
@click.option('--quiet', '-q', is_flag=True, help='Output only the content (for text) or suppress messages (for files)')
@click.option('--force', is_flag=True, help='Overwrite existing output file')
def decode_cmd(ref, stego, phrase, pin, key, key_password, output, quiet, force):
def decode_cmd(ref, stego, phrase, pin, key, key_qr, key_password, output, quiet, force):
"""
Decode a secret message or file from a stego image.
Must use the same credentials that were used for encoding.
Automatically detects whether content is text or a file.
RSA key can be provided as a .pem file (--key) or QR code image (--key-qr).
\b
Examples:
# Decode and print text
# Decode with PIN
stegasoo decode -r photo.jpg -s stego.png -p "apple forest thunder" --pin 123456
# Decode and save (auto-detect type)
stegasoo decode -r photo.jpg -s stego.png -p "words" --pin 123456 -o output.txt
# Decode with RSA key file
stegasoo decode -r photo.jpg -s stego.png -p "words" -k mykey.pem
# Quiet mode for piping text
stegasoo decode -r photo.jpg -s stego.png -p "words" --pin 123456 -q | less
# Decode with RSA key from QR code image
stegasoo decode -r photo.jpg -s stego.png -p "words" --key-qr keyqr.png
# Save output to file
stegasoo decode -r photo.jpg -s stego.png -p "words" --pin 123456 -o output.txt
"""
# Load key if provided
# Load key if provided (from .pem file or QR code image)
rsa_key_data = None
rsa_key_from_qr = False
if key and key_qr:
raise click.UsageError("Cannot use both --key and --key-qr. Choose one.")
if key:
rsa_key_data = Path(key).read_bytes()
elif key_qr:
if not HAS_QR or not has_qr_read():
raise click.ClickException(
"QR code reading not available. Install: pip install pyzbar\n"
"Also requires system library: sudo apt-get install libzbar0"
)
key_pem = extract_key_from_qr_file(key_qr)
if not key_pem:
raise click.ClickException(f"Could not extract RSA key from QR code: {key_qr}")
rsa_key_data = key_pem.encode('utf-8')
rsa_key_from_qr = True
if not quiet:
click.echo(f"Loaded RSA key from QR code: {key_qr}")
# QR code keys are never password-protected
effective_key_password = None if rsa_key_from_qr else key_password
# Validate security factors
if not pin and not rsa_key_data:
raise click.UsageError("Must provide --pin or --key (or both)")
raise click.UsageError("Must provide --pin or --key/--key-qr (or both)")
try:
ref_photo = Path(ref).read_bytes()
@@ -320,7 +383,7 @@ def decode_cmd(ref, stego, phrase, pin, key, key_password, output, quiet, force)
day_phrase=phrase,
pin=pin or "",
rsa_key_data=rsa_key_data,
rsa_password=key_password,
rsa_password=effective_key_password,
)
if result.is_file: