Skip to content

Instantly share code, notes, and snippets.

Last active August 13, 2022 15:31
What would you like to do?
Tinyscript steganography tool based on base32/64 padding


This Tinyscript-based tool allows to unhide data hidden in base32/base64 strings. It can take a PNG or JPG in input to retrieve an EXIF value as the input data.

This can be installed using:

$ pip install tinyscript
$ wget && chmod +x && sudo mv /usr/bin/paddinganograph
$ paddinganograph -e base64 -f Comment -s . < test.jpg
$ paddinganograph -e base64 -f Comment -s . < test.jpg | paddinganograph -e base32

This tool is especially useful in the use cases hereafter.

Retrieve hidden data from an image using Base32/64 padding

Select the "Comment" field, split its value with the separator "." and extract hidden data:

$ paddinganograph -s "." -f "Comment" < image.jpg
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import math
from tinyscript import *
__author__ = "Alexandre D'Hondt"
__version__ = "1.5"
__copyright__ = ("A. D'Hondt", 2019)
__license__ = "gpl-3.0"
__reference__ = ""
__examples__ = ["-s . -f \"Comment\" < image.jpg > base32.enc", "-e base32 < base32.enc"]
__docformat__ = "md"
__doc__ = """
*Paddinganograph* allows to unhide data hidden in base32/base64 strings. It can take a PNG or JPG in input to retrieve an EXIF value as the input data.
DEF_BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def exif(raw_data, key):
t = ts.TempPath().tempfile().name
with open(str(t), 'wb') as f:
logger.debug("Getting EXIF data...")
exif = subprocess.check_output(["exiftool", str(t)])
exif = codecs.decode(exif, "utf-8")
exif = {l.split(':', 1)[0].strip(): l.split(':', 1)[1].strip() for l in exif.split('\n') if l.strip() != ""}
return exif if not key else exif[key]
def unhide(encoded, encoding="base64", charset=None, sep=".", pad="=", n_pad=8):
charset = (charset or globals()["DEF_{}".format(encoding.upper())]).strip()
except NameError:
raise ValueError("Bad encoding")
logger.debug("Unhidding data...")
bits = b("")
for token in b(encoded).split(b(sep)):
bits += unhide_bits(token.strip(), charset, pad, n_pad) or b("")
return b("").join(ts.bin2str(bits[i:i+8]) for i in range(0, len(bits), 8))
def unhide_bits(encoded, charset, pad="=", n_pad=8):
def __gcd(a,b):
while b > 0:
a, b = b, a % b
return a
padding = encoded.count(b(pad))
n_repr = int(math.ceil(math.log(len(charset), 2)))
w_len = n_repr * n_pad / __gcd(n_repr, n_pad)
n_char = int(math.ceil(float(w_len) / n_repr))
if encoded == "" or len(encoded) % n_char != 0 or padding == 0:
unused = {n: int(w_len - n * n_repr) % n_pad for n in range(n_char)}
b_val = bin(b(charset).index(encoded.rstrip(b(pad))[-1]))[2:].zfill(n_repr)
return b(b_val[-unused[padding]:])
if __name__ == '__main__':
parser.add_argument("-c", "--charset", help="characters set")
parser.add_argument("-e", "--encoding", choices=["base32", "base64"], default="base64", help="character encoding")
parser.add_argument("-f", "--exif-field", dest="exif", default="Comment", help="EXIF metadata field to be selected")
parser.add_argument("-p", "--padding-char", default="=", help="padding character")
parser.add_argument("-s", "--separator", default="\n", help="base-encoded token separator")
data = b("").join(l for l in ts.stdin_pipe())
if data.startswith(b("\x89PNG")) or data.startswith(b("\xff\xd8\xff\xe0")):
data = exif(data, args.exif)
except KeyError:
logger.error("No EXIF field '%s'" % args.exif)
print(ensure_str(unhide(data, args.encoding, args.charset, args.separator)))
Copy link

dhondta commented Aug 13, 2022

Salut @Tom-Weber !
Merci pour ces explications.
Après pip3 install --upgrade markdown, j'ai :

$ pip show markdown
Name: Markdown
Version: 3.4.1
Summary: Python implementation of Markdown.

Et j'obtiens bien l'erreur que tu as rapportée :

$ paddinganograph --help
Traceback (most recent call last):
ImportError: cannot import name 'etree' from 'markdown.util' (/home/morfal/.local/lib/python3.8/site-packages/markdown/

Le problème vient bien de mdv qui n'est pas compatible avec markdown>=3.4 apparemment.
Je peux épingler markdown==3.3.7 temporairement pour résoudre le problème.
Merci en tout cas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment