Cosmin Poieana
Sr. Software Engineer seasoned with business and leadership skills. Passionate about startups, photography, videography, art, reading and traveling the world.
%PDF-1.7
1 0 obj % entry point
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj % page content
<<
/Length 44
>>
stream
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000380 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
492
%%EOF
%PDF-1.7
1 0 obj % entry point
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Typ#65 /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj % page content
<<
/L#65ngth 44
>>
stream
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000382 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
496
%%EOF
#! /usr/bin/env python3
# 05.04.2010 <> 06.04.2010 | cmiN
# Text In Bmp (console)
import sys
from hashlib import md5
class Image:
def load(self, path):
with open(path, "rb") as file:
buffer = file.read()
self.bfType = buffer[0:2]
if self.bfType != b"BM":
raise Exception("not a bitmap")
self.bfSize = buffer[2:6]
self.bfReserved1 = buffer[6:8]
self.bfReserved2 = buffer[8:10]
self.bfOffBits = buffer[10:14]
self.biSize = buffer[14:18]
self.biWidth = buffer[18:22]
self.biHeight = buffer[22:26]
self.biPlanes = buffer[26:28]
self.biBitCount = buffer[28:30]
if baconvert(self.biBitCount) != 24:
raise Exception("not 24-bit")
self.biCompression = buffer[30:34]
self.biSizeImage = buffer[34:38]
self.biXPelsPerMeter = buffer[38:42]
self.biYPelsPerMeter = buffer[42:46]
self.biClrUsed = buffer[46:50]
self.biClrImportant = buffer[50:54]
self.bHeader = buffer[:54]
self.bMatrix = list(buffer[54:])
def create(self, path, buffer):
with open(path, "wb") as file:
file.write(buffer)
def process(digsig, mode, infile, outfile=None, string=None):
bmp = Image()
bmp.load(infile)
bmp.width = baconvert(bmp.biWidth)
bmp.height = baconvert(bmp.biHeight)
bmp.index = 0
bmp.count = 0
rem = (bmp.width * 3) % 4
if rem:
bmp.padding = 4 - rem
else:
bmp.padding = 0
if mode == "write":
bits = str()
for char in md5(bytes(digsig, "ascii")).digest():
bits += bin(char).replace("0b", "").zfill(8)
bits += bin(len(string)).replace("0b", "").zfill(16)
for char in string:
bits += bin(ord(char)).replace("0b", "").zfill(8)
if len(bits) > bmp.width * bmp.height * 3:
raise Exception("string too long")
for bit in bits:
char = bin(bmp.bMatrix[bmp.index])
char = int(char[:-1] + bit, 2)
bmp.bMatrix[bmp.index] = char
bmp.index += 1
bmp.count += 1
if bmp.count == (bmp.width * 3):
bmp.count = 0
bmp.index += bmp.padding
bmp.create(outfile, bmp.bHeader + bytes(bmp.bMatrix))
elif mode == "read":
bits = bitjoin(bmp, 128)
if bytes([int(bits[i:i + 8], 2) for i in range(0, 128, 8)]) == md5(bytes(digsig, "ascii")).digest():
nr = int(bitjoin(bmp, 16), 2) * 8
bits = bitjoin(bmp, nr)
string = "".join([chr(int(bits[i:i + 8], 2)) for i in range(0, nr, 8)])
print(string)
else:
raise Exception("invalid signature")
else:
raise Exception("invalid mode")
def bitjoin(bmp, nr):
bits = str()
for i in range(nr):
bits += bin(bmp.bMatrix[bmp.index])[-1]
bmp.index += 1
bmp.count += 1
if bmp.count == (bmp.width * 3):
bmp.count = 0
bmp.index += bmp.padding
return bits
def baconvert(buffer):
return int("".join([hex(char).replace("0x", "").zfill(2) for char in reversed(buffer)]), 16)
def main(args):
usage = """\t\t Text In Bmp 1.0
\t Usage: source.ext digsig mode infile [outfile text]
Where digsig is a digital signature string
mode can be write or read
infile is a valid 24-bit bitmap image
outfile is the output image name (used with write mode)
text is the string that will be written in image (used with write mode)
\t Example: tib.py cmiN write image1.bmp image2.bmp http://rstcenter.com
\t tib.py cmiN read image2.bmp"""
try:
print("Please wait...")
if len(args) == 4:
process(args[1], args[2], args[3])
elif len(args) == 6:
process(args[1], args[2], args[3], args[4], args[5])
else:
print(usage)
except Exception as message:
print("An error occurred: {}".format(message))
except:
print("Unknown error.")
else:
print("Ready!")
if __name__ == "__main__":
main(sys.argv)
$ ./tib.py sparks2014 write pylogo.bmp out.bmp "Hello, world!"
Please wait...
Ready!
$ ./tib.py sparks2014 read out.bmp
Please wait...
Hello, world!
Ready!
$ ./tib.py sparks2013 read out.bmp
Please wait...
An error occurred: invalid signature
#! /usr/bin/env python
# Text In Image
# 02.01.2012 cmiN
#
# This is a simple GUI script which can hide text in pictures
# using least significant bit method.
# Also the input text can be encrypted and the output can be decrypted too
# with a symmetric key using AES.
# Writing is done directly on input image so be careful with certain extensions
# because the output will always have the BMP format.
#
# Contact: cmin764@yahoo/gmail.com
import os
from Tkinter import * # widgets's classes
from tkFileDialog import askopenfilename # get file name
from tkMessageBox import showerror, showinfo # user dialog
from PIL import Image # image converting
from Crypto.Cipher import AES # text cipher
class Engine:
"""
Code for processing the image.
Separated from GUI.
"""
def __init__(self):
""" Initialize parameters. """
self.ext = "bmp" # save format
self.name = None # save name
self.path = None # save path
self.im = None # image object, read and write
self.generator = None # get locations to write/read bits
self.useAES = None # use it or not
self.aes = None # AES object
self.data = None # data to be written to image
self.width = None # image width
self.height = None # image height
self.tmp = None # last string, used when key changes
def binary(self, nr, size):
""" Get 1&0 representation. """
return bin(nr).replace("0b", "").zfill(size * 8)
def path_name(self, path):
""" Split a file path in path and name. """
ind = path.rfind("/") + 1
return (path[:ind], path[ind:])
def set_generator(self):
""" Useful for resetting. """
self.generator = ((wp, hp, ch) for wp in xrange(self.width) # WxHxC
for hp in xrange(self.height)
for ch in xrange(3))
def load(self, path):
""" Load image. """
self.im = Image.open(path)
(self.width, self.height) = self.im.size
(self.path, self.name) = self.path_name(path)
return self.width * self.height * 3 # total useful bytes
def parse_key(self, key):
""" If key exists make an AES object from it. """
if not key:
self.aes = None # empty key == no encryption
return self.parse_string(self.tmp) # must return size (see the next return)
key.decode() # test availability
size = len(key)
for padding in (16, 24, 32): # fixed key size
if size <= padding:
break
key += chr(0) * (padding - size)
self.aes = AES.new(key)
return self.parse_string(self.tmp) # if key changes you must update string
def parse_string(self, string):
""" Convert to bitstring. """
if not string: # without string can't start the process
self.tmp = None
self.data = None
return 0
string.decode() # test availability
self.tmp = string
if self.useAES and self.aes: # encrypt it
string += chr(0) * ((16 - len(string) % 16) % 16) # multiple of 16 string
string = self.aes.encrypt(string)
string = str().join([self.binary(ord(x), 1) for x in string]) # convert every char in a set of 8 bits
size = self.binary(len(string), 4) # get binary representation of string's length in 4 bytes
self.data = size + string
return len(self.data)
def write(self):
""" Write using LSB. """
self.set_generator() # rearm
for bit in self.data:
(wp, hp, ch) = self.generator.next() # get next position
values = list(self.im.getpixel((wp, hp))) # retrieve its values
tmp = self.binary(values[ch], 1) # convert one of them
values[ch] = int(tmp[:7] + bit, 2) # alter that channel
self.im.putpixel((wp, hp), tuple(values)) # put it back
self.im.save(self.path + self.name, format=self.ext) # save the new output
def read(self):
""" Read from every LSB. """
self.set_generator() # rearm
total = self.width * self.height * 3
if total < 32:
raise Exception("Text not found.")
size = chunk = string = str()
i = 0 # for(i=0; true; ++i)
while True:
(wp, hp, ch) = self.generator.next() # i byte
values = self.im.getpixel((wp, hp))
tmp = self.binary(values[ch], 1)
if i < 32: # it's lame but I prefer string/bitset
size += tmp[7]
if i == 31:
size = int(size, 2)
if size < 1 or (size + 32) > total:
raise Exception("Text not found.")
elif i < size + 32:
chunk += tmp[7]
if len(chunk) == 8:
string += chr(int(chunk, 2))
chunk = str()
else:
break
i += 1
if self.useAES and self.aes:
if len(string) % 16 != 0:
raise Exception("Text not encrypted.")
string = self.aes.decrypt(string).rstrip(chr(0))
string.decode() # rise an exception if invalid
return string
class GUI(Frame):
"""
Main window, inherited from Frame.
Here we put our widgets and set their behavior.
"""
def __init__(self, master=None, margin=30):
""" Same as Frame's constructor. """
Frame.__init__(self, master, padx=margin, pady=margin)
self.grid()
self.widgets()
self.behavior()
def widgets(self):
""" Build and grid widgets. """
# ---- create variables ----
self.totalBytes = IntVar() # depends on image size
self.usedBytes = IntVar() # how many of them are used
self.textStatus = StringVar() # used per total bytes
self.useEncryption = IntVar() # 0-plain 1-AES
self.mode = IntVar() # 0-read 1-write
self.textOpt = dict() # text last config
self.keyOpt = dict() # key last config
self.loaded = False # image loaded or not
# ---- create widgets ----
self.label = Label(self, textvariable=self.textStatus)
self.about = Label(self, text="About", fg="blue")
self.text = Text(self, width=30, height=5, fg="grey")
self.scrollbar = Scrollbar(self, orient="vertical", command=self.text.yview)
self.loadButton = Button(self, text="Load", width=5, command=lambda: self.action("load"))
self.readRadio = Radiobutton(self, text="Read", variable=self.mode, value=0, command=self.set_state)
self.checkButton = Checkbutton(self, text="Use AES", variable=self.useEncryption, onvalue=1, offvalue=0, command=self.set_state)
self.startButton = Button(self, text="Start", width=5, state="disabled", command=lambda: self.action("start"))
self.writeRadio = Radiobutton(self, text="Write", variable=self.mode, value=1, command=self.set_state)
self.keyEntry = Entry(self, width=10, fg="grey", show="*")
# ---- show widgets ----
self.label.grid(row=0, column=0, columnspan=2, sticky="w")
self.about.grid(row=0, column=2, sticky="e")
self.text.grid(row=1, column=0, rowspan=3, columnspan=3)
self.scrollbar.grid(row=1, column=3, rowspan=3, sticky="ns")
self.loadButton.grid(row=4, column=0, sticky="w", pady=5)
self.readRadio.grid(row=4, column=1)
self.checkButton.grid(row=4, column=2, sticky="e")
self.startButton.grid(row=5, column=0, sticky="w")
self.writeRadio.grid(row=5, column=1)
self.keyEntry.grid(row=5, column=2, sticky="e")
def behavior(self):
""" Customize widgets. """
self.text.config(yscrollcommand=self.scrollbar.set)
self.text.insert(0.0, "Text here")
self.keyEntry.insert(0, "Key here")
self.text.bind("<Button>", self.handle_event)
self.text.bind("<KeyRelease>", self.handle_event)
self.keyEntry.bind("<Button>", self.handle_event)
self.keyEntry.bind("<KeyRelease>", self.handle_event)
self.textOpt = self.get_opt(self.text)
self.keyOpt = self.get_opt(self.keyEntry)
self.about.bind("<Button>", self.handle_event)
self.set_state()
def action(self, arg):
""" What every button triggers. """
if arg == "load":
fileTypes = [("BMP", "*.bmp"), ("JPEG", ("*.jpeg", "*.jpg")), ("PNG", "*.png"), ("All Files", "*.*")]
path = askopenfilename(parent=self, title="Open image", filetypes=fileTypes)
if path != "":
try:
self.totalBytes.set(app.load(path))
except IOError as msg:
showerror("Error", str(msg).capitalize().strip(".") + ".") # some formatting
else:
self.loaded = True
self.set_state()
self.master.title("Text In Image - %s" % app.name) # update name in title
elif arg == "start":
if self.mode.get():
try:
app.write()
except Exception as msg:
showerror("Error", str(msg).capitalize().strip(".") + ".")
else:
showinfo("Info", "Done.")
else:
try:
string = app.read()
except UnicodeError:
showerror("Error", "Text not found or wrong key.")
except Exception as msg:
showerror("Error", str(msg).capitalize().strip(".") + ".")
else:
self.text.config(state="normal")
self.textOpt["fg"] = "black" # touched
self.text.delete(0.0, END)
self.text.insert(0.0, string)
self.text.config(state="disabled")
self.usedBytes.set(app.parse_string(string))
self.set_status()
showinfo("Info", "Done.")
def set_status(self):
""" Get used per total bytes. """
string = "%9.3f%s/%9.3f%s"
unit1 = unit2 = "b"
used = self.usedBytes.get()
total = self.totalBytes.get()
if used > total:
self.label.config(fg="red")
else:
self.label.config(fg="black")
if used > 999999:
unit1 = "Mb"
used /= 1000000.0
elif used > 999:
unit1 = "Kb"
used /= 1000.0
if total > 999999:
unit2 = "Mb"
total /= 1000000.0
elif total > 999:
unit2 = "Kb"
total /= 1000.0
self.textStatus.set(string % (used, unit1, total, unit2))
def get_opt(self, widget):
""" Get some options from a widget then pack them. """
opt = dict()
opt["state"] = widget["state"]
opt["fg"] = widget["fg"]
opt["bg"] = widget["bg"]
return opt
def set_state(self):
""" Enable or disable a widget according to option selected. """
if self.mode.get(): # write
self.text.config(**self.textOpt)
else:
self.text.config(state="disabled", bg="lightgrey", fg="darkgrey")
if self.useEncryption.get(): # use AES
self.keyEntry.config(**self.keyOpt)
app.useAES = True
else:
self.keyEntry.config(state="disabled")
app.useAES = False
length = app.parse_string(app.tmp)
self.usedBytes.set(length)
self.set_status()
if self.loaded: # a file is loaded
if self.mode.get() == 0: # read mode
ok = True
elif app.data != None and self.usedBytes.get() <= self.totalBytes.get():
ok = True
else:
ok = False
else:
ok = False # no file loaded
if ok:
self.startButton.config(state="normal")
else:
self.startButton.config(state="disabled")
def handle_event(self, event):
""" Handle events for specific widgets. """
if event.widget is self.text and self.text["state"] == "normal":
if self.text["fg"] == "grey":
self.text.delete(0.0, END)
self.textOpt["fg"] = self.text["fg"] = "black"
string = self.text.get(0.0, END).strip()
try:
length = app.parse_string(string)
except UnicodeError:
showerror("Error", "Invalid text.")
else:
self.usedBytes.set(length)
self.set_state()
elif event.widget is self.keyEntry and self.keyEntry["state"] == "normal":
if self.keyEntry["fg"] == "grey":
self.keyEntry.delete(0, END)
self.keyOpt["fg"] = self.keyEntry["fg"] = "black"
key = self.keyEntry.get()[:32] # first 32 (max size is 32)
try:
length = app.parse_key(key)
except UnicodeError:
showerror("Error", "Invalid key.")
else:
self.usedBytes.set(length)
self.set_state()
elif event.widget is self.about:
showinfo("About", "Hide text, which can be encrypted with AES, in pictures (bitmaps). Coded by cmiN. Visit rstforums.com.")
if __name__ == "__main__":
app = Engine() # core
root = Tk() # toplevel
root.title("Text In Image")
root.maxsize(350, 250)
icon = "tii.ico"
if os.path.isfile(icon):
root.iconbitmap(icon)
GUI(root)
root.mainloop()
By Cosmin Poieana
În această prelegere, voi discuta despre “arta” de a ascunde date în fișiere a căror tipuri ne sunt cunoscute și pe care le întâlnim zilnic. De asemenea, vor fi prezentate și câteva metode triviale de securitate, dar și o întrebuințare practică a acestei proceduri.
Sr. Software Engineer seasoned with business and leadership skills. Passionate about startups, photography, videography, art, reading and traveling the world.